mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Console] Delete unused sense models and unused files (#195344)
This commit is contained in:
parent
f2b9348f97
commit
d273c07edc
61 changed files with 9 additions and 10277 deletions
31
NOTICE.txt
31
NOTICE.txt
|
@ -74,37 +74,6 @@ under a "BSD" license.
|
|||
|
||||
Distributed under the BSD license:
|
||||
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Ajax.org B.V. nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
This product includes code that is based on Ace editor, which was available
|
||||
under a "BSD" license.
|
||||
|
||||
Distributed under the BSD license:
|
||||
|
||||
Copyright (c) 2010, Ajax.org B.V.
|
||||
All rights reserved.
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ POST /_some_endpoint
|
|||
```
|
||||
|
||||
## Architecture
|
||||
Console uses Ace editor that is wrapped with [`CoreEditor`](https://github.com/elastic/kibana/blob/main/src/plugins/console/public/types/core_editor.ts), so that if needed it can easily be replaced with another editor, for example Monaco.
|
||||
Console uses Monaco editor that is wrapped with [`kbn-monaco`](https://github.com/elastic/kibana/blob/main/packages/kbn-monaco/index.ts), so that if needed it can easily be replaced with another editor.
|
||||
The autocomplete logic is located in [`autocomplete`](https://github.com/elastic/kibana/blob/main/src/plugins/console/public/lib/autocomplete) folder. Autocomplete rules are computed by classes in `components` sub-folder.
|
||||
|
||||
## Autocomplete definitions
|
||||
|
@ -317,8 +317,4 @@ Another change is replacing jQuery with the core http client to communicate with
|
|||
### Outstanding issues
|
||||
#### Autocomplete suggestions for Kibana API endpoints
|
||||
Console currently supports autocomplete suggestions for Elasticsearch API endpoints. The autocomplete suggestions for Kibana API endpoints are not supported yet.
|
||||
Related issue: [#130661](https://github.com/elastic/kibana/issues/130661)
|
||||
|
||||
#### Migration to Monaco Editor
|
||||
Console plugin is currently using Ace Editor and it is planned to migrate to Monaco Editor in the future.
|
||||
Related issue: [#57435](https://github.com/elastic/kibana/issues/57435)
|
||||
Related issue: [#130661](https://github.com/elastic/kibana/issues/130661)
|
|
@ -8,12 +8,11 @@
|
|||
*/
|
||||
|
||||
import { MonacoEditorActionsProvider } from '../../containers/editor/monaco_editor_actions_provider';
|
||||
import { SenseEditor } from '../../models/sense_editor';
|
||||
|
||||
export class EditorRegistry {
|
||||
private inputEditor: SenseEditor | MonacoEditorActionsProvider | undefined;
|
||||
private inputEditor: MonacoEditorActionsProvider | undefined;
|
||||
|
||||
setInputEditor(inputEditor: SenseEditor | MonacoEditorActionsProvider) {
|
||||
setInputEditor(inputEditor: MonacoEditorActionsProvider) {
|
||||
this.inputEditor = inputEditor;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
|
||||
export { useSetInputEditor } from './use_set_input_editor';
|
||||
export { useRestoreRequestFromHistory } from './use_restore_request_from_history';
|
||||
export { useSendCurrentRequest, sendRequest } from './use_send_current_request';
|
||||
export { sendRequest } from './use_send_current_request';
|
||||
export { useSaveCurrentTextObject } from './use_save_current_text_object';
|
||||
export { useDataInit } from './use_data_init';
|
||||
|
|
|
@ -1,10 +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".
|
||||
*/
|
||||
|
||||
export { useRestoreRequestFromHistory } from './use_restore_request_from_history';
|
|
@ -1,48 +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 RowParser from '../../../lib/row_parser';
|
||||
import { ESRequest } from '../../../types';
|
||||
import { SenseEditor } from '../../models/sense_editor';
|
||||
import { formatRequestBodyDoc } from '../../../lib/utils';
|
||||
|
||||
export function restoreRequestFromHistory(editor: SenseEditor, req: ESRequest) {
|
||||
const coreEditor = editor.getCoreEditor();
|
||||
let pos = coreEditor.getCurrentPosition();
|
||||
let prefix = '';
|
||||
let suffix = '\n';
|
||||
const parser = new RowParser(coreEditor);
|
||||
if (parser.isStartRequestRow(pos.lineNumber)) {
|
||||
pos.column = 1;
|
||||
suffix += '\n';
|
||||
} else if (parser.isEndRequestRow(pos.lineNumber)) {
|
||||
const line = coreEditor.getLineValue(pos.lineNumber);
|
||||
pos.column = line.length + 1;
|
||||
prefix = '\n\n';
|
||||
} else if (parser.isInBetweenRequestsRow(pos.lineNumber)) {
|
||||
pos.column = 1;
|
||||
} else {
|
||||
pos = editor.nextRequestEnd(pos);
|
||||
prefix = '\n\n';
|
||||
}
|
||||
|
||||
let s = prefix + req.method + ' ' + req.endpoint;
|
||||
if (req.data) {
|
||||
const indent = true;
|
||||
const formattedData = formatRequestBodyDoc([req.data], indent);
|
||||
s += '\n' + formattedData.data;
|
||||
}
|
||||
|
||||
s += suffix;
|
||||
|
||||
coreEditor.insert(pos, s);
|
||||
coreEditor.moveCursorToPosition({ lineNumber: pos.lineNumber + prefix.length, column: 1 });
|
||||
coreEditor.clearSelection();
|
||||
coreEditor.getContainer().focus();
|
||||
}
|
|
@ -1,25 +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 { formatRequestBodyDoc } from '../../../lib/utils';
|
||||
import { MonacoEditorActionsProvider } from '../../containers/editor/monaco_editor_actions_provider';
|
||||
import { ESRequest } from '../../../types';
|
||||
|
||||
export async function restoreRequestFromHistoryToMonaco(
|
||||
provider: MonacoEditorActionsProvider,
|
||||
req: ESRequest
|
||||
) {
|
||||
let s = req.method + ' ' + req.endpoint;
|
||||
if (req.data) {
|
||||
const indent = true;
|
||||
const formattedData = formatRequestBodyDoc([req.data], indent);
|
||||
s += '\n' + formattedData.data;
|
||||
}
|
||||
await provider.restoreRequestFromHistory(s);
|
||||
}
|
|
@ -1,21 +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 { useCallback } from 'react';
|
||||
import { instance as registry } from '../../contexts/editor_context/editor_registry';
|
||||
import { ESRequest } from '../../../types';
|
||||
import { restoreRequestFromHistoryToMonaco } from './restore_request_from_history_to_monaco';
|
||||
import { MonacoEditorActionsProvider } from '../../containers/editor/monaco_editor_actions_provider';
|
||||
|
||||
export const useRestoreRequestFromHistory = () => {
|
||||
return useCallback(async (req: ESRequest) => {
|
||||
const editor = registry.getInputEditor();
|
||||
await restoreRequestFromHistoryToMonaco(editor as MonacoEditorActionsProvider, req);
|
||||
}, []);
|
||||
};
|
|
@ -7,5 +7,4 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export { useSendCurrentRequest } from './use_send_current_request';
|
||||
export { sendRequest } from './send_request';
|
||||
|
|
|
@ -1,34 +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 { SenseEditor } from '../../models/sense_editor';
|
||||
import { getEndpointFromPosition } from '../../../lib/autocomplete/get_endpoint_from_position';
|
||||
import { MetricsTracker } from '../../../types';
|
||||
|
||||
export const track = (
|
||||
requests: Array<{ method: string }>,
|
||||
editor: SenseEditor,
|
||||
trackUiMetric: MetricsTracker
|
||||
) => {
|
||||
const coreEditor = editor.getCoreEditor();
|
||||
// `getEndpointFromPosition` gets values from the server-side generated JSON files which
|
||||
// are a combination of JS, automatically generated JSON and manual overrides. That means
|
||||
// the metrics reported from here will be tied to the definitions in those files.
|
||||
// See src/legacy/core_plugins/console/server/api_server/spec
|
||||
const endpointDescription = getEndpointFromPosition(
|
||||
coreEditor,
|
||||
coreEditor.getCurrentPosition(),
|
||||
editor.parser
|
||||
);
|
||||
|
||||
if (requests[0] && endpointDescription) {
|
||||
const eventName = `${requests[0].method}_${endpointDescription.id ?? 'unknown'}`;
|
||||
trackUiMetric.count(eventName);
|
||||
}
|
||||
};
|
|
@ -1,130 +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".
|
||||
*/
|
||||
|
||||
jest.mock('./send_request', () => ({ sendRequest: jest.fn() }));
|
||||
jest.mock('../../contexts/editor_context/editor_registry', () => ({
|
||||
instance: { getInputEditor: jest.fn() },
|
||||
}));
|
||||
jest.mock('./track', () => ({ track: jest.fn() }));
|
||||
jest.mock('../../contexts/request_context', () => ({ useRequestActionContext: jest.fn() }));
|
||||
jest.mock('../../../lib/utils', () => ({ replaceVariables: jest.fn() }));
|
||||
|
||||
import React from 'react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
|
||||
import { ContextValue, ServicesContextProvider } from '../../contexts';
|
||||
import { serviceContextMock } from '../../contexts/services_context.mock';
|
||||
import { useRequestActionContext } from '../../contexts/request_context';
|
||||
import { instance as editorRegistry } from '../../contexts/editor_context/editor_registry';
|
||||
import * as utils from '../../../lib/utils';
|
||||
|
||||
import { sendRequest } from './send_request';
|
||||
import { useSendCurrentRequest } from './use_send_current_request';
|
||||
|
||||
describe('useSendCurrentRequest', () => {
|
||||
let mockContextValue: ContextValue;
|
||||
let dispatch: (...args: unknown[]) => void;
|
||||
const contexts = ({ children }: { children: JSX.Element }) => (
|
||||
<ServicesContextProvider value={mockContextValue}>{children}</ServicesContextProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
mockContextValue = serviceContextMock.create();
|
||||
dispatch = jest.fn();
|
||||
(useRequestActionContext as jest.Mock).mockReturnValue(dispatch);
|
||||
(utils.replaceVariables as jest.Mock).mockReturnValue(['test']);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('calls send request', async () => {
|
||||
// Set up mocks
|
||||
(mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({});
|
||||
// This request should succeed
|
||||
(sendRequest as jest.Mock).mockResolvedValue([]);
|
||||
(editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({
|
||||
getRequestsInRange: () => ['test'],
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts });
|
||||
await act(() => result.current());
|
||||
expect(sendRequest).toHaveBeenCalledWith({
|
||||
http: mockContextValue.services.http,
|
||||
requests: ['test'],
|
||||
});
|
||||
|
||||
// Second call should be the request success
|
||||
const [, [requestSucceededCall]] = (dispatch as jest.Mock).mock.calls;
|
||||
expect(requestSucceededCall).toEqual({ type: 'requestSuccess', payload: { data: [] } });
|
||||
});
|
||||
|
||||
it('handles known errors', async () => {
|
||||
// Set up mocks
|
||||
(sendRequest as jest.Mock).mockRejectedValue({ response: 'nada' });
|
||||
(editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({
|
||||
getRequestsInRange: () => ['test'],
|
||||
}));
|
||||
|
||||
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;
|
||||
|
||||
// The request must have concluded
|
||||
expect(requestFailedCall).toEqual({ type: 'requestFail', payload: { response: 'nada' } });
|
||||
});
|
||||
|
||||
it('handles unknown errors', async () => {
|
||||
// Set up mocks
|
||||
(sendRequest as jest.Mock).mockRejectedValue(NaN /* unexpected error value */);
|
||||
(editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({
|
||||
getRequestsInRange: () => ['test'],
|
||||
}));
|
||||
|
||||
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;
|
||||
|
||||
// The request must have concluded
|
||||
expect(requestFailedCall).toEqual({ type: 'requestFail', payload: undefined });
|
||||
// It also notified the user
|
||||
expect(mockContextValue.services.notifications.toasts.addError).toHaveBeenCalledWith(NaN, {
|
||||
title: 'Unknown Request Error',
|
||||
});
|
||||
});
|
||||
|
||||
it('notifies the user about save to history errors once only', async () => {
|
||||
// Set up mocks
|
||||
(sendRequest as jest.Mock).mockReturnValue(
|
||||
[{ request: {} }, { request: {} }] /* two responses to save history */
|
||||
);
|
||||
(mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({
|
||||
isHistoryEnabled: true,
|
||||
});
|
||||
(mockContextValue.services.history.addToHistory as jest.Mock).mockImplementation(() => {
|
||||
// Mock throwing
|
||||
throw new Error('cannot save!');
|
||||
});
|
||||
(editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({
|
||||
getRequestsInRange: () => ['test', 'test'],
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts });
|
||||
await act(() => result.current());
|
||||
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(mockContextValue.services.history.addToHistory).toHaveBeenCalledTimes(2);
|
||||
// It only called notification once
|
||||
expect(mockContextValue.services.notifications.toasts.addError).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -1,148 +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 { i18n } from '@kbn/i18n';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { toMountPoint } from '../../../shared_imports';
|
||||
import { isQuotaExceededError } from '../../../services/history';
|
||||
import { instance as registry } from '../../contexts/editor_context/editor_registry';
|
||||
import { useRequestActionContext, useServicesContext } from '../../contexts';
|
||||
import { StorageQuotaError } from '../../components/storage_quota_error';
|
||||
import { sendRequest } from './send_request';
|
||||
import { track } from './track';
|
||||
import { replaceVariables } from '../../../lib/utils';
|
||||
import { StorageKeys } from '../../../services';
|
||||
import { DEFAULT_VARIABLES } from '../../../../common/constants';
|
||||
import { SenseEditor } from '../../models';
|
||||
|
||||
export const useSendCurrentRequest = () => {
|
||||
const {
|
||||
services: { history, settings, notifications, trackUiMetric, http, autocompleteInfo, storage },
|
||||
...startServices
|
||||
} = useServicesContext();
|
||||
|
||||
const dispatch = useRequestActionContext();
|
||||
|
||||
return useCallback(async () => {
|
||||
try {
|
||||
const editor = registry.getInputEditor() as SenseEditor;
|
||||
const variables = storage.get(StorageKeys.VARIABLES, DEFAULT_VARIABLES);
|
||||
let requests = await editor.getRequestsInRange();
|
||||
requests = replaceVariables(requests, variables);
|
||||
if (!requests.length) {
|
||||
notifications.toasts.add(
|
||||
i18n.translate('console.notification.error.noRequestSelectedTitle', {
|
||||
defaultMessage:
|
||||
'No request selected. Select a request by placing the cursor inside it.',
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: 'sendRequest', payload: undefined });
|
||||
|
||||
// Fire and forget
|
||||
setTimeout(() => track(requests, editor as SenseEditor, trackUiMetric), 0);
|
||||
|
||||
const results = await sendRequest({ http, requests });
|
||||
|
||||
let saveToHistoryError: undefined | Error;
|
||||
const { isHistoryEnabled } = settings.toJSON();
|
||||
|
||||
if (isHistoryEnabled) {
|
||||
results.forEach(({ request: { path, method, data } }) => {
|
||||
try {
|
||||
history.addToHistory(path, method, data);
|
||||
} catch (e) {
|
||||
// Grab only the first error
|
||||
if (!saveToHistoryError) {
|
||||
saveToHistoryError = e;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (saveToHistoryError) {
|
||||
const errorTitle = i18n.translate('console.notification.error.couldNotSaveRequestTitle', {
|
||||
defaultMessage: 'Could not save request to Console history.',
|
||||
});
|
||||
if (isQuotaExceededError(saveToHistoryError)) {
|
||||
const toast = notifications.toasts.addWarning({
|
||||
title: i18n.translate('console.notification.error.historyQuotaReachedMessage', {
|
||||
defaultMessage:
|
||||
'Request history is full. Clear the console history or disable saving new requests.',
|
||||
}),
|
||||
text: toMountPoint(
|
||||
StorageQuotaError({
|
||||
onClearHistory: () => {
|
||||
history.clearHistory();
|
||||
notifications.toasts.remove(toast);
|
||||
},
|
||||
onDisableSavingToHistory: () => {
|
||||
settings.setIsHistoryEnabled(false);
|
||||
notifications.toasts.remove(toast);
|
||||
},
|
||||
}),
|
||||
startServices
|
||||
),
|
||||
});
|
||||
} else {
|
||||
// Best effort, but still notify the user.
|
||||
notifications.toasts.addError(saveToHistoryError, {
|
||||
title: errorTitle,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { polling } = settings.toJSON();
|
||||
if (polling) {
|
||||
// If the user has submitted a request against ES, something in the fields, indices, aliases,
|
||||
// or templates may have changed, so we'll need to update this data. Assume that if
|
||||
// the user disables polling they're trying to optimize performance or otherwise
|
||||
// preserve resources, so they won't want this request sent either.
|
||||
autocompleteInfo.retrieve(settings, settings.getAutocomplete());
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'requestSuccess',
|
||||
payload: {
|
||||
data: results,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
if (e?.response) {
|
||||
dispatch({
|
||||
type: 'requestFail',
|
||||
payload: e,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'requestFail',
|
||||
payload: undefined,
|
||||
});
|
||||
notifications.toasts.addError(e, {
|
||||
title: i18n.translate('console.notification.error.unknownErrorTitle', {
|
||||
defaultMessage: 'Unknown Request Error',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
storage,
|
||||
dispatch,
|
||||
http,
|
||||
settings,
|
||||
notifications.toasts,
|
||||
trackUiMetric,
|
||||
history,
|
||||
autocompleteInfo,
|
||||
startServices,
|
||||
]);
|
||||
};
|
|
@ -10,14 +10,13 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useEditorActionContext } from '../contexts/editor_context';
|
||||
import { instance as registry } from '../contexts/editor_context/editor_registry';
|
||||
import { SenseEditor } from '../models';
|
||||
import { MonacoEditorActionsProvider } from '../containers/editor/monaco_editor_actions_provider';
|
||||
|
||||
export const useSetInputEditor = () => {
|
||||
const dispatch = useEditorActionContext();
|
||||
|
||||
return useCallback(
|
||||
(editor: SenseEditor | MonacoEditorActionsProvider) => {
|
||||
(editor: MonacoEditorActionsProvider) => {
|
||||
dispatch({ type: 'setInputEditor', payload: editor });
|
||||
registry.setInputEditor(editor);
|
||||
},
|
||||
|
|
|
@ -1,11 +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".
|
||||
*/
|
||||
|
||||
export * from './legacy_core_editor/legacy_core_editor';
|
||||
export * from './sense_editor';
|
|
@ -1,20 +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 ace from 'brace';
|
||||
import { LegacyCoreEditor } from './legacy_core_editor';
|
||||
|
||||
export const create = (el: HTMLElement) => {
|
||||
const actions = document.querySelector<HTMLElement>('#ConAppEditorActions');
|
||||
if (!actions) {
|
||||
throw new Error('Could not find ConAppEditorActions element!');
|
||||
}
|
||||
const aceEditor = ace.edit(el);
|
||||
return new LegacyCoreEditor(aceEditor, actions);
|
||||
};
|
|
@ -1,81 +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 _ from 'lodash';
|
||||
import ace from 'brace';
|
||||
import { Mode } from './mode/output';
|
||||
import smartResize from './smart_resize';
|
||||
|
||||
export interface CustomAceEditor extends ace.Editor {
|
||||
update: (text: string, mode?: string | Mode, cb?: () => void) => void;
|
||||
append: (text: string, foldPrevious?: boolean, cb?: () => void) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: using read-only ace editor leaks the Ace editor API - use this as sparingly as possible or
|
||||
* create an interface for it so that we don't rely directly on vendor APIs.
|
||||
*/
|
||||
export function createReadOnlyAceEditor(element: HTMLElement): CustomAceEditor {
|
||||
const output: CustomAceEditor = ace.acequire('ace/ace').edit(element);
|
||||
|
||||
const outputMode = new Mode();
|
||||
|
||||
output.$blockScrolling = Infinity;
|
||||
output.resize = smartResize(output);
|
||||
output.update = (val, mode, cb) => {
|
||||
if (typeof mode === 'function') {
|
||||
cb = mode as () => void;
|
||||
mode = void 0;
|
||||
}
|
||||
|
||||
const session = output.getSession();
|
||||
const currentMode = val ? mode || outputMode : 'ace/mode/text';
|
||||
|
||||
// @ts-ignore
|
||||
// ignore ts error here due to type definition mistake in brace for setMode(mode: string): void;
|
||||
// this method accepts string or SyntaxMode which is an object. See https://github.com/ajaxorg/ace/blob/13dc911dbc0ea31ca343d5744b3f472767458fc3/ace.d.ts#L467
|
||||
session.setMode(currentMode);
|
||||
session.setValue(val);
|
||||
if (typeof cb === 'function') {
|
||||
setTimeout(cb);
|
||||
}
|
||||
};
|
||||
|
||||
output.append = (val: string, foldPrevious?: boolean, cb?: () => void) => {
|
||||
if (typeof foldPrevious === 'function') {
|
||||
cb = foldPrevious;
|
||||
foldPrevious = true;
|
||||
}
|
||||
if (_.isUndefined(foldPrevious)) {
|
||||
foldPrevious = true;
|
||||
}
|
||||
const session = output.getSession();
|
||||
const lastLine = session.getLength();
|
||||
if (foldPrevious) {
|
||||
output.moveCursorTo(Math.max(0, lastLine - 1), 0);
|
||||
}
|
||||
session.insert({ row: lastLine, column: 0 }, '\n' + val);
|
||||
output.moveCursorTo(lastLine + 1, 0);
|
||||
if (typeof cb === 'function') {
|
||||
setTimeout(cb);
|
||||
}
|
||||
};
|
||||
|
||||
(function setupSession(session) {
|
||||
session.setMode('ace/mode/text');
|
||||
(session as unknown as { setFoldStyle: (v: string) => void }).setFoldStyle('markbeginend');
|
||||
session.setTabSize(2);
|
||||
session.setUseWrapMode(true);
|
||||
})(output.getSession());
|
||||
|
||||
output.setShowPrintMargin(false);
|
||||
output.setReadOnly(true);
|
||||
|
||||
return output;
|
||||
}
|
|
@ -1,18 +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 'brace';
|
||||
import 'brace/ext/language_tools';
|
||||
import 'brace/ext/searchbox';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/text';
|
||||
|
||||
export * from './legacy_core_editor';
|
||||
export * from './create_readonly';
|
||||
export * from './create';
|
|
@ -1,559 +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 './legacy_core_editor.test.mocks';
|
||||
import RowParser from '../../../lib/row_parser';
|
||||
import { createTokenIterator } from '../../factories';
|
||||
import $ from 'jquery';
|
||||
import { create } from './create';
|
||||
|
||||
describe('Input', () => {
|
||||
let coreEditor;
|
||||
beforeEach(() => {
|
||||
// Set up our document body
|
||||
document.body.innerHTML = `<div>
|
||||
<div id="ConAppEditor" />
|
||||
<div id="ConAppEditorActions" />
|
||||
<div id="ConCopyAsCurl" />
|
||||
</div>`;
|
||||
|
||||
coreEditor = create(document.querySelector('#ConAppEditor'));
|
||||
|
||||
$(coreEditor.getContainer()).show();
|
||||
});
|
||||
afterEach(() => {
|
||||
$(coreEditor.getContainer()).hide();
|
||||
});
|
||||
|
||||
describe('.getLineCount', () => {
|
||||
it('returns the correct line length', async () => {
|
||||
await coreEditor.setValue('1\n2\n3\n4', true);
|
||||
expect(coreEditor.getLineCount()).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tokenization', () => {
|
||||
function tokensAsList() {
|
||||
const iter = createTokenIterator({
|
||||
editor: coreEditor,
|
||||
position: { lineNumber: 1, column: 1 },
|
||||
});
|
||||
const ret = [];
|
||||
let t = iter.getCurrentToken();
|
||||
const parser = new RowParser(coreEditor);
|
||||
if (parser.isEmptyToken(t)) {
|
||||
t = parser.nextNonEmptyToken(iter);
|
||||
}
|
||||
while (t) {
|
||||
ret.push({ value: t.value, type: t.type });
|
||||
t = parser.nextNonEmptyToken(iter);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let testCount = 0;
|
||||
|
||||
function tokenTest(tokenList, prefix, data) {
|
||||
if (data && typeof data !== 'string') {
|
||||
data = JSON.stringify(data, null, 3);
|
||||
}
|
||||
if (data) {
|
||||
if (prefix) {
|
||||
data = prefix + '\n' + data;
|
||||
}
|
||||
} else {
|
||||
data = prefix;
|
||||
}
|
||||
|
||||
test('Token test ' + testCount++ + ' prefix: ' + prefix, async function () {
|
||||
await coreEditor.setValue(data, true);
|
||||
const tokens = tokensAsList();
|
||||
const normTokenList = [];
|
||||
for (let i = 0; i < tokenList.length; i++) {
|
||||
normTokenList.push({ type: tokenList[i++], value: tokenList[i] });
|
||||
}
|
||||
|
||||
expect(tokens).toEqual(normTokenList);
|
||||
});
|
||||
}
|
||||
|
||||
tokenTest(['method', 'GET', 'url.part', '_search'], 'GET _search');
|
||||
|
||||
tokenTest(['method', 'GET', 'url.slash', '/', 'url.part', '_search'], 'GET /_search');
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.protocol_host',
|
||||
'http://somehost',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_search',
|
||||
],
|
||||
'GET http://somehost/_search'
|
||||
);
|
||||
|
||||
tokenTest(['method', 'GET', 'url.protocol_host', 'http://somehost'], 'GET http://somehost');
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.protocol_host', 'http://somehost', 'url.slash', '/'],
|
||||
'GET http://somehost/'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.protocol_host', 'http://test:user@somehost', 'url.slash', '/'],
|
||||
'GET http://test:user@somehost/'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.part', '_cluster', 'url.slash', '/', 'url.part', 'nodes'],
|
||||
'GET _cluster/nodes'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_cluster',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'nodes',
|
||||
],
|
||||
'GET /_cluster/nodes'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.part', 'index', 'url.slash', '/', 'url.part', '_search'],
|
||||
'GET index/_search'
|
||||
);
|
||||
|
||||
tokenTest(['method', 'GET', 'url.part', 'index'], 'GET index');
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.part', 'index', 'url.slash', '/', 'url.part', 'type'],
|
||||
'GET index/type'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type',
|
||||
'url.slash',
|
||||
'/',
|
||||
],
|
||||
'GET /index/type/'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.part',
|
||||
'index',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_search',
|
||||
],
|
||||
'GET index/type/_search'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.part',
|
||||
'index',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_search',
|
||||
'url.questionmark',
|
||||
'?',
|
||||
'url.param',
|
||||
'value',
|
||||
'url.equal',
|
||||
'=',
|
||||
'url.value',
|
||||
'1',
|
||||
],
|
||||
'GET index/type/_search?value=1'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.part',
|
||||
'index',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'1',
|
||||
],
|
||||
'GET index/type/1'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
'url.slash',
|
||||
'/',
|
||||
],
|
||||
'GET /index1,index2/'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_search',
|
||||
],
|
||||
'GET /index1,index2/_search'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'_search',
|
||||
],
|
||||
'GET index1,index2/_search'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'GET',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
],
|
||||
'GET /index1,index2'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.part', 'index1', 'url.comma', ',', 'url.part', 'index2'],
|
||||
'GET index1,index2'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.slash', '/', 'url.part', 'index1', 'url.comma', ','],
|
||||
'GET /index1,'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'PUT', 'url.slash', '/', 'url.part', 'index', 'url.slash', '/'],
|
||||
'PUT /index/'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
['method', 'GET', 'url.part', 'index', 'url.slash', '/', 'url.part', '_search'],
|
||||
'GET index/_search '
|
||||
);
|
||||
|
||||
tokenTest(['method', 'PUT', 'url.slash', '/', 'url.part', 'index'], 'PUT /index');
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'PUT',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'type2',
|
||||
],
|
||||
'PUT /index1,index2/type1,type2'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'PUT',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'type2',
|
||||
'url.comma',
|
||||
',',
|
||||
],
|
||||
'PUT /index1/type1,type2,'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'PUT',
|
||||
'url.part',
|
||||
'index1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'index2',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'type1',
|
||||
'url.comma',
|
||||
',',
|
||||
'url.part',
|
||||
'type2',
|
||||
'url.slash',
|
||||
'/',
|
||||
'url.part',
|
||||
'1234',
|
||||
],
|
||||
'PUT index1,index2/type1,type2/1234'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'POST',
|
||||
'url.part',
|
||||
'_search',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'variable',
|
||||
'"q"',
|
||||
'punctuation.colon',
|
||||
':',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
],
|
||||
'POST _search\n' + '{\n' + ' "q": {}\n' + ' \n' + '}'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'method',
|
||||
'POST',
|
||||
'url.part',
|
||||
'_search',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'variable',
|
||||
'"q"',
|
||||
'punctuation.colon',
|
||||
':',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'variable',
|
||||
'"s"',
|
||||
'punctuation.colon',
|
||||
':',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
],
|
||||
'POST _search\n' + '{\n' + ' "q": { "s": {}}\n' + ' \n' + '}'
|
||||
);
|
||||
|
||||
function statesAsList() {
|
||||
const ret = [];
|
||||
const maxLine = coreEditor.getLineCount();
|
||||
for (let line = 1; line <= maxLine; line++) ret.push(coreEditor.getLineState(line));
|
||||
return ret;
|
||||
}
|
||||
|
||||
function statesTest(statesList, prefix, data) {
|
||||
if (data && typeof data !== 'string') {
|
||||
data = JSON.stringify(data, null, 3);
|
||||
}
|
||||
if (data) {
|
||||
if (prefix) {
|
||||
data = prefix + '\n' + data;
|
||||
}
|
||||
} else {
|
||||
data = prefix;
|
||||
}
|
||||
|
||||
test('States test ' + testCount++ + ' prefix: ' + prefix, async function () {
|
||||
await coreEditor.setValue(data, true);
|
||||
const modes = statesAsList();
|
||||
expect(modes).toEqual(statesList);
|
||||
});
|
||||
}
|
||||
|
||||
statesTest(
|
||||
['start', 'json', 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "query": { "match_all": {} }\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', ['json', 'json'], ['json', 'json'], 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "query": { \n' + ' "match_all": {} \n' + ' }\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "script": { "source": "" }\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "script": ""\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', ['json', 'json'], 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "script": {\n' + ' }\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
[
|
||||
'start',
|
||||
'json',
|
||||
['script-start', 'json', 'json', 'json'],
|
||||
['script-start', 'json', 'json', 'json'],
|
||||
['json', 'json'],
|
||||
'json',
|
||||
'start',
|
||||
],
|
||||
'POST _search\n' +
|
||||
'{\n' +
|
||||
' "test": { "script": """\n' +
|
||||
' test script\n' +
|
||||
' """\n' +
|
||||
' }\n' +
|
||||
'}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', ['script-start', 'json'], ['script-start', 'json'], 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "script": """\n' + ' test script\n' + ' """,\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "script": """test script""",\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', ['string_literal', 'json'], ['string_literal', 'json'], 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "something": """\n' + ' test script\n' + ' """,\n' + '}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
[
|
||||
'start',
|
||||
'json',
|
||||
['string_literal', 'json', 'json', 'json'],
|
||||
['string_literal', 'json', 'json', 'json'],
|
||||
['json', 'json'],
|
||||
['json', 'json'],
|
||||
'json',
|
||||
'start',
|
||||
],
|
||||
'POST _search\n' +
|
||||
'{\n' +
|
||||
' "something": { "f" : """\n' +
|
||||
' test script\n' +
|
||||
' """,\n' +
|
||||
' "g": 1\n' +
|
||||
' }\n' +
|
||||
'}'
|
||||
);
|
||||
|
||||
statesTest(
|
||||
['start', 'json', 'json', 'start'],
|
||||
'POST _search\n' + '{\n' + ' "something": """test script""",\n' + '}'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,29 +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".
|
||||
*/
|
||||
|
||||
jest.mock('./mode/worker', () => {
|
||||
return { workerModule: { id: 'sense_editor/mode/worker', src: '' } };
|
||||
});
|
||||
|
||||
import '@kbn/web-worker-stub';
|
||||
|
||||
// @ts-ignore
|
||||
window.URL = {
|
||||
createObjectURL: () => {
|
||||
return '';
|
||||
},
|
||||
};
|
||||
|
||||
import 'brace';
|
||||
import 'brace/ext/language_tools';
|
||||
import 'brace/ext/searchbox';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/text';
|
||||
|
||||
document.queryCommandSupported = () => true;
|
|
@ -1,511 +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 ace, { type Annotation } from 'brace';
|
||||
import { Editor as IAceEditor, IEditSession as IAceEditSession } from 'brace';
|
||||
import $ from 'jquery';
|
||||
import {
|
||||
CoreEditor,
|
||||
Position,
|
||||
Range,
|
||||
Token,
|
||||
TokensProvider,
|
||||
EditorEvent,
|
||||
AutoCompleterFunction,
|
||||
} from '../../../types';
|
||||
import { AceTokensProvider } from '../../../lib/ace_token_provider';
|
||||
import * as curl from '../sense_editor/curl';
|
||||
import smartResize from './smart_resize';
|
||||
import * as InputMode from './mode/input';
|
||||
|
||||
const _AceRange = ace.acequire('ace/range').Range;
|
||||
|
||||
const rangeToAceRange = ({ start, end }: Range) =>
|
||||
new _AceRange(start.lineNumber - 1, start.column - 1, end.lineNumber - 1, end.column - 1);
|
||||
|
||||
export class LegacyCoreEditor implements CoreEditor {
|
||||
private _aceOnPaste: Function;
|
||||
$actions: JQuery<HTMLElement>;
|
||||
resize: () => void;
|
||||
|
||||
constructor(private readonly editor: IAceEditor, actions: HTMLElement) {
|
||||
this.$actions = $(actions);
|
||||
this.editor.setShowPrintMargin(false);
|
||||
|
||||
const session = this.editor.getSession();
|
||||
// @ts-expect-error
|
||||
// ignore ts error here due to type definition mistake in brace for setMode(mode: string): void;
|
||||
// this method accepts string or SyntaxMode which is an object. See https://github.com/ajaxorg/ace/blob/13dc911dbc0ea31ca343d5744b3f472767458fc3/ace.d.ts#L467
|
||||
session.setMode(new InputMode.Mode());
|
||||
(session as unknown as { setFoldStyle: (style: string) => void }).setFoldStyle('markbeginend');
|
||||
session.setTabSize(2);
|
||||
session.setUseWrapMode(true);
|
||||
|
||||
this.resize = smartResize(this.editor);
|
||||
|
||||
// Intercept ace on paste handler.
|
||||
this._aceOnPaste = this.editor.onPaste;
|
||||
this.editor.onPaste = this.DO_NOT_USE_onPaste.bind(this);
|
||||
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
});
|
||||
|
||||
this.editor.$blockScrolling = Infinity;
|
||||
this.hideActionsBar();
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
// dirty check for tokenizer state, uses a lot less cycles
|
||||
// than listening for tokenizerUpdate
|
||||
waitForLatestTokens(): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
const session = this.editor.getSession();
|
||||
const checkInterval = 25;
|
||||
|
||||
const check = () => {
|
||||
// If the bgTokenizer doesn't exist, we can assume that the underlying editor has been
|
||||
// torn down, e.g. by closing the History tab, and we don't need to do anything further.
|
||||
if (session.bgTokenizer) {
|
||||
// Wait until the bgTokenizer is done running before executing the callback.
|
||||
if ((session.bgTokenizer as unknown as { running: boolean }).running) {
|
||||
setTimeout(check, checkInterval);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(check, 0);
|
||||
});
|
||||
}
|
||||
|
||||
getLineState(lineNumber: number) {
|
||||
const session = this.editor.getSession();
|
||||
return session.getState(lineNumber - 1);
|
||||
}
|
||||
|
||||
getValueInRange(range: Range): string {
|
||||
return this.editor.getSession().getTextRange(rangeToAceRange(range));
|
||||
}
|
||||
|
||||
getTokenProvider(): TokensProvider {
|
||||
return new AceTokensProvider(this.editor.getSession());
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.editor.getValue();
|
||||
}
|
||||
|
||||
async setValue(text: string, forceRetokenize: boolean): Promise<void> {
|
||||
const session = this.editor.getSession();
|
||||
session.setValue(text);
|
||||
if (forceRetokenize) {
|
||||
await this.forceRetokenize();
|
||||
}
|
||||
}
|
||||
|
||||
getLineValue(lineNumber: number): string {
|
||||
const session = this.editor.getSession();
|
||||
return session.getLine(lineNumber - 1);
|
||||
}
|
||||
|
||||
getCurrentPosition(): Position {
|
||||
const cursorPosition = this.editor.getCursorPosition();
|
||||
return {
|
||||
lineNumber: cursorPosition.row + 1,
|
||||
column: cursorPosition.column + 1,
|
||||
};
|
||||
}
|
||||
|
||||
clearSelection(): void {
|
||||
this.editor.clearSelection();
|
||||
}
|
||||
|
||||
getTokenAt(pos: Position): Token | null {
|
||||
const provider = this.getTokenProvider();
|
||||
return provider.getTokenAt(pos);
|
||||
}
|
||||
|
||||
insert(valueOrPos: string | Position, value?: string): void {
|
||||
if (typeof valueOrPos === 'string') {
|
||||
this.editor.insert(valueOrPos);
|
||||
return;
|
||||
}
|
||||
const document = this.editor.getSession().getDocument();
|
||||
document.insert(
|
||||
{
|
||||
column: valueOrPos.column - 1,
|
||||
row: valueOrPos.lineNumber - 1,
|
||||
},
|
||||
value || ''
|
||||
);
|
||||
}
|
||||
|
||||
moveCursorToPosition(pos: Position): void {
|
||||
this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 });
|
||||
}
|
||||
|
||||
replace(range: Range, value: string): void {
|
||||
const session = this.editor.getSession();
|
||||
session.replace(rangeToAceRange(range), value);
|
||||
}
|
||||
|
||||
getLines(startLine: number, endLine: number): string[] {
|
||||
const session = this.editor.getSession();
|
||||
return session.getLines(startLine - 1, endLine - 1);
|
||||
}
|
||||
|
||||
replaceRange(range: Range, value: string) {
|
||||
const pos = this.editor.getCursorPosition();
|
||||
this.editor.getSession().replace(rangeToAceRange(range), value);
|
||||
|
||||
const maxRow = Math.max(range.start.lineNumber - 1 + value.split('\n').length - 1, 1);
|
||||
pos.row = Math.min(pos.row, maxRow);
|
||||
this.editor.moveCursorToPosition(pos);
|
||||
// ACE UPGRADE - check if needed - at the moment the above may trigger a selection.
|
||||
this.editor.clearSelection();
|
||||
}
|
||||
|
||||
getSelectionRange() {
|
||||
const result = this.editor.getSelectionRange();
|
||||
return {
|
||||
start: {
|
||||
lineNumber: result.start.row + 1,
|
||||
column: result.start.column + 1,
|
||||
},
|
||||
end: {
|
||||
lineNumber: result.end.row + 1,
|
||||
column: result.end.column + 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getLineCount() {
|
||||
// Only use this function to return line count as it uses
|
||||
// a cache.
|
||||
return this.editor.getSession().getLength();
|
||||
}
|
||||
|
||||
addMarker(range: Range) {
|
||||
return this.editor
|
||||
.getSession()
|
||||
.addMarker(rangeToAceRange(range), 'ace_snippet-marker', 'fullLine', false);
|
||||
}
|
||||
|
||||
removeMarker(ref: number) {
|
||||
this.editor.getSession().removeMarker(ref);
|
||||
}
|
||||
|
||||
getWrapLimit(): number {
|
||||
return this.editor.getSession().getWrapLimit();
|
||||
}
|
||||
|
||||
on(event: EditorEvent, listener: () => void) {
|
||||
if (event === 'changeCursor') {
|
||||
this.editor.getSession().selection.on(event, listener);
|
||||
} else if (event === 'changeSelection') {
|
||||
this.editor.on(event, listener);
|
||||
} else {
|
||||
this.editor.getSession().on(event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
off(event: EditorEvent, listener: () => void) {
|
||||
if (event === 'changeSelection') {
|
||||
this.editor.off(event, listener);
|
||||
}
|
||||
}
|
||||
|
||||
isCompleterActive() {
|
||||
return Boolean(
|
||||
(this.editor as unknown as { completer: { activated: unknown } }).completer &&
|
||||
(this.editor as unknown as { completer: { activated: unknown } }).completer.activated
|
||||
);
|
||||
}
|
||||
|
||||
detachCompleter() {
|
||||
// In some situations we need to detach the autocomplete suggestions element manually,
|
||||
// such as when navigating away from Console when the suggestions list is open.
|
||||
const completer = (this.editor as unknown as { completer: { detach(): void } }).completer;
|
||||
return completer?.detach();
|
||||
}
|
||||
|
||||
private forceRetokenize() {
|
||||
const session = this.editor.getSession();
|
||||
return new Promise<void>((resolve) => {
|
||||
// force update of tokens, but not on this thread to allow for ace rendering.
|
||||
setTimeout(function () {
|
||||
let i;
|
||||
for (i = 0; i < session.getLength(); i++) {
|
||||
session.getTokens(i);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private DO_NOT_USE_onPaste(text: string) {
|
||||
if (text && curl.detectCURL(text)) {
|
||||
const curlInput = curl.parseCURL(text);
|
||||
this.editor.insert(curlInput);
|
||||
return;
|
||||
}
|
||||
this._aceOnPaste.call(this.editor, text);
|
||||
}
|
||||
|
||||
private setActionsBar = (value: number | null, topOrBottom: 'top' | 'bottom' = 'top') => {
|
||||
if (value === null) {
|
||||
this.$actions.css('visibility', 'hidden');
|
||||
} else {
|
||||
if (topOrBottom === 'top') {
|
||||
this.$actions.css({
|
||||
bottom: 'auto',
|
||||
top: value,
|
||||
visibility: 'visible',
|
||||
});
|
||||
} else {
|
||||
this.$actions.css({
|
||||
top: 'auto',
|
||||
bottom: value,
|
||||
visibility: 'visible',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private hideActionsBar = () => {
|
||||
this.setActionsBar(null);
|
||||
};
|
||||
|
||||
execCommand(cmd: string) {
|
||||
this.editor.execCommand(cmd);
|
||||
}
|
||||
|
||||
getContainer(): HTMLDivElement {
|
||||
return this.editor.container as HTMLDivElement;
|
||||
}
|
||||
|
||||
setStyles(styles: { wrapLines: boolean; fontSize: string }) {
|
||||
this.editor.getSession().setUseWrapMode(styles.wrapLines);
|
||||
this.editor.container.style.fontSize = styles.fontSize;
|
||||
}
|
||||
|
||||
registerKeyboardShortcut(opts: { keys: string; fn: () => void; name: string }): void {
|
||||
this.editor.commands.addCommand({
|
||||
exec: opts.fn,
|
||||
name: opts.name,
|
||||
bindKey: opts.keys,
|
||||
});
|
||||
}
|
||||
|
||||
unregisterKeyboardShortcut(command: string) {
|
||||
// @ts-ignore
|
||||
this.editor.commands.removeCommand(command);
|
||||
}
|
||||
|
||||
legacyUpdateUI(range: Range) {
|
||||
if (!this.$actions) {
|
||||
return;
|
||||
}
|
||||
if (range) {
|
||||
// elements are positioned relative to the editor's container
|
||||
// pageY is relative to page, so subtract the offset
|
||||
// from pageY to get the new top value
|
||||
const offsetFromPage = $(this.editor.container).offset()!.top;
|
||||
const startLine = range.start.lineNumber;
|
||||
const startColumn = range.start.column;
|
||||
const firstLine = this.getLineValue(startLine);
|
||||
const maxLineLength = this.getWrapLimit() - 5;
|
||||
const isWrapping = firstLine.length > maxLineLength;
|
||||
const totalOffset = offsetFromPage - (window.pageYOffset || 0);
|
||||
const getScreenCoords = (line: number) =>
|
||||
this.editor.renderer.textToScreenCoordinates(line - 1, startColumn).pageY - totalOffset;
|
||||
const topOfReq = getScreenCoords(startLine);
|
||||
|
||||
if (topOfReq >= 0) {
|
||||
const { bottom: maxBottom } = this.editor.container.getBoundingClientRect();
|
||||
if (topOfReq > maxBottom - totalOffset) {
|
||||
this.setActionsBar(0, 'bottom');
|
||||
return;
|
||||
}
|
||||
let offset = 0;
|
||||
if (isWrapping) {
|
||||
// Try get the line height of the text area in pixels.
|
||||
const textArea = $(this.editor.container.querySelector('textArea')!);
|
||||
const hasRoomOnNextLine = this.getLineValue(startLine).length < maxLineLength;
|
||||
if (textArea && hasRoomOnNextLine) {
|
||||
// Line height + the number of wraps we have on a line.
|
||||
offset += this.getLineValue(startLine).length * textArea.height()!;
|
||||
} else {
|
||||
if (startLine > 1) {
|
||||
this.setActionsBar(getScreenCoords(startLine - 1));
|
||||
return;
|
||||
}
|
||||
this.setActionsBar(getScreenCoords(startLine + 1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.setActionsBar(topOfReq + offset);
|
||||
return;
|
||||
}
|
||||
|
||||
const bottomOfReq =
|
||||
this.editor.renderer.textToScreenCoordinates(range.end.lineNumber, range.end.column).pageY -
|
||||
offsetFromPage;
|
||||
|
||||
if (bottomOfReq >= 0) {
|
||||
this.setActionsBar(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerAutocompleter(autocompleter: AutoCompleterFunction): void {
|
||||
// Hook into Ace
|
||||
|
||||
// disable standard context based autocompletion.
|
||||
// @ts-ignore
|
||||
ace.define(
|
||||
'ace/autocomplete/text_completer',
|
||||
['require', 'exports', 'module'],
|
||||
function (
|
||||
require: unknown,
|
||||
exports: {
|
||||
getCompletions: (
|
||||
innerEditor: unknown,
|
||||
session: unknown,
|
||||
pos: unknown,
|
||||
prefix: unknown,
|
||||
callback: (e: null | Error, values: string[]) => void
|
||||
) => void;
|
||||
}
|
||||
) {
|
||||
exports.getCompletions = function (innerEditor, session, pos, prefix, callback) {
|
||||
callback(null, []);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const langTools = ace.acequire('ace/ext/language_tools');
|
||||
|
||||
langTools.setCompleters([
|
||||
{
|
||||
identifierRegexps: [
|
||||
/[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/, // adds support for dot character
|
||||
],
|
||||
getCompletions: (
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
DO_NOT_USE_1: IAceEditor,
|
||||
aceEditSession: IAceEditSession,
|
||||
pos: { row: number; column: number },
|
||||
prefix: string,
|
||||
callback: (...args: unknown[]) => void
|
||||
) => {
|
||||
const position: Position = {
|
||||
lineNumber: pos.row + 1,
|
||||
column: pos.column + 1,
|
||||
};
|
||||
|
||||
const getAnnotationControls = () => {
|
||||
let customAnnotation: Annotation;
|
||||
return {
|
||||
setAnnotation(text: string) {
|
||||
const annotations = aceEditSession.getAnnotations();
|
||||
customAnnotation = {
|
||||
text,
|
||||
row: pos.row,
|
||||
column: pos.column,
|
||||
type: 'warning',
|
||||
};
|
||||
|
||||
aceEditSession.setAnnotations([...annotations, customAnnotation]);
|
||||
},
|
||||
removeAnnotation() {
|
||||
aceEditSession.setAnnotations(
|
||||
aceEditSession.getAnnotations().filter((a: Annotation) => a !== customAnnotation)
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
autocompleter(position, prefix, callback, getAnnotationControls());
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.editor.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats body of the request in the editor by removing the extra whitespaces at the beginning of lines,
|
||||
* And adds the correct indentation for each line
|
||||
* @param reqRange request range to indent
|
||||
*/
|
||||
autoIndent(reqRange: Range) {
|
||||
const session = this.editor.getSession();
|
||||
const mode = session.getMode();
|
||||
const startRow = reqRange.start.lineNumber;
|
||||
const endRow = reqRange.end.lineNumber;
|
||||
const tab = session.getTabString();
|
||||
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
let prevLineState = '';
|
||||
let prevLineIndent = '';
|
||||
if (row > 0) {
|
||||
prevLineState = session.getState(row - 1);
|
||||
const prevLine = session.getLine(row - 1);
|
||||
prevLineIndent = mode.getNextLineIndent(prevLineState, prevLine, tab);
|
||||
}
|
||||
|
||||
const line = session.getLine(row);
|
||||
// @ts-ignore
|
||||
// Brace does not expose type definition for mode.$getIndent, though we have access to this method provided by the underlying Ace editor.
|
||||
// See https://github.com/ajaxorg/ace/blob/87ce087ed1cf20eeabe56fb0894e048d9bc9c481/lib/ace/mode/text.js#L259
|
||||
const currLineIndent = mode.$getIndent(line);
|
||||
if (prevLineIndent !== currLineIndent) {
|
||||
if (currLineIndent.length > 0) {
|
||||
// If current line has indentation, remove it.
|
||||
// Next we will add the correct indentation by looking at the previous line
|
||||
const range = new _AceRange(row, 0, row, currLineIndent.length);
|
||||
session.remove(range);
|
||||
}
|
||||
if (prevLineIndent.length > 0) {
|
||||
// If previous line has indentation, add indentation at the current line
|
||||
session.insert({ row, column: 0 }, prevLineIndent);
|
||||
}
|
||||
}
|
||||
|
||||
// Lastly outdent any closing braces
|
||||
mode.autoOutdent(prevLineState, session, row);
|
||||
}
|
||||
}
|
||||
|
||||
getAllFoldRanges(): Range[] {
|
||||
const session = this.editor.getSession();
|
||||
// @ts-ignore
|
||||
// Brace does not expose type definition for session.getAllFolds, though we have access to this method provided by the underlying Ace editor.
|
||||
// See https://github.com/ajaxorg/ace/blob/13dc911dbc0ea31ca343d5744b3f472767458fc3/ace.d.ts#L82
|
||||
return session.getAllFolds().map((fold) => fold.range);
|
||||
}
|
||||
|
||||
addFoldsAtRanges(foldRanges: Range[]) {
|
||||
const session = this.editor.getSession();
|
||||
foldRanges.forEach((range) => {
|
||||
try {
|
||||
session.addFold('...', _AceRange.fromPoints(range.start, range.end));
|
||||
} catch (e) {
|
||||
// ignore the error if a fold fails
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,79 +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 ace from 'brace';
|
||||
import { workerModule } from './worker';
|
||||
import { ScriptMode } from './script';
|
||||
|
||||
const TextMode = ace.acequire('ace/mode/text').Mode;
|
||||
|
||||
const MatchingBraceOutdent = ace.acequire('ace/mode/matching_brace_outdent').MatchingBraceOutdent;
|
||||
const CstyleBehaviour = ace.acequire('ace/mode/behaviour/cstyle').CstyleBehaviour;
|
||||
const CStyleFoldMode = ace.acequire('ace/mode/folding/cstyle').FoldMode;
|
||||
const WorkerClient = ace.acequire('ace/worker/worker_client').WorkerClient;
|
||||
const AceTokenizer = ace.acequire('ace/tokenizer').Tokenizer;
|
||||
|
||||
import { InputHighlightRules } from './input_highlight_rules';
|
||||
|
||||
export class Mode extends TextMode {
|
||||
constructor() {
|
||||
super();
|
||||
this.$tokenizer = new AceTokenizer(new InputHighlightRules().getRules());
|
||||
this.$outdent = new MatchingBraceOutdent();
|
||||
this.$behaviour = new CstyleBehaviour();
|
||||
this.foldingRules = new CStyleFoldMode();
|
||||
this.createModeDelegates({
|
||||
'script-': ScriptMode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(function (this: Mode) {
|
||||
this.getCompletions = function () {
|
||||
// autocomplete is done by the autocomplete module.
|
||||
return [];
|
||||
};
|
||||
|
||||
this.getNextLineIndent = function (state: string, line: string, tab: string) {
|
||||
let indent = this.$getIndent(line);
|
||||
|
||||
if (state !== 'string_literal') {
|
||||
const match = line.match(/^.*[\{\(\[]\s*$/);
|
||||
if (match) {
|
||||
indent += tab;
|
||||
}
|
||||
}
|
||||
|
||||
return indent;
|
||||
};
|
||||
|
||||
this.checkOutdent = function (state: unknown, line: string, input: string) {
|
||||
return this.$outdent.checkOutdent(line, input);
|
||||
};
|
||||
|
||||
this.autoOutdent = function (state: unknown, doc: string, row: string) {
|
||||
this.$outdent.autoOutdent(doc, row);
|
||||
};
|
||||
this.createWorker = function (session: {
|
||||
getDocument: () => string;
|
||||
setAnnotations: (arg0: unknown) => void;
|
||||
}) {
|
||||
const worker = new WorkerClient(['ace', 'sense_editor'], workerModule, 'SenseWorker');
|
||||
worker.attachToDocument(session.getDocument());
|
||||
worker.on('error', function (e: { data: unknown }) {
|
||||
session.setAnnotations([e.data]);
|
||||
});
|
||||
|
||||
worker.on('ok', function (anno: { data: unknown }) {
|
||||
session.setAnnotations(anno.data);
|
||||
});
|
||||
|
||||
return worker;
|
||||
};
|
||||
}).call(Mode.prototype);
|
|
@ -1,180 +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 ace from 'brace';
|
||||
import { addXJsonToRules } from '@kbn/ace';
|
||||
|
||||
type Token =
|
||||
| string
|
||||
| { token?: string; regex?: string; next?: string; push?: boolean; include?: string };
|
||||
|
||||
export function addEOL(
|
||||
tokens: Token[],
|
||||
reg: string | RegExp,
|
||||
nextIfEOL: string,
|
||||
normalNext?: string
|
||||
) {
|
||||
if (typeof reg === 'object') {
|
||||
reg = reg.source;
|
||||
}
|
||||
return [
|
||||
{ token: tokens.concat(['whitespace']), regex: reg + '(\\s*)$', next: nextIfEOL },
|
||||
{ token: tokens, regex: reg, next: normalNext },
|
||||
];
|
||||
}
|
||||
|
||||
export const mergeTokens = (...args: any[]) => [].concat.apply([], args);
|
||||
|
||||
const TextHighlightRules = ace.acequire('ace/mode/text_highlight_rules').TextHighlightRules;
|
||||
// translating this to monaco
|
||||
export class InputHighlightRules extends TextHighlightRules {
|
||||
constructor() {
|
||||
super();
|
||||
this.$rules = {
|
||||
// TODO
|
||||
'start-sql': [
|
||||
{ token: 'whitespace', regex: '\\s+' },
|
||||
{ token: 'paren.lparen', regex: '{', next: 'json-sql', push: true },
|
||||
{ regex: '', next: 'start' },
|
||||
],
|
||||
start: mergeTokens(
|
||||
[
|
||||
// done
|
||||
{ token: 'warning', regex: '#!.*$' },
|
||||
// done
|
||||
{ include: 'comments' },
|
||||
// done
|
||||
{ token: 'paren.lparen', regex: '{', next: 'json', push: true },
|
||||
],
|
||||
// done
|
||||
addEOL(['method'], /([a-zA-Z]+)/, 'start', 'method_sep'),
|
||||
[
|
||||
// done
|
||||
{
|
||||
token: 'whitespace',
|
||||
regex: '\\s+',
|
||||
},
|
||||
// done
|
||||
{
|
||||
token: 'text',
|
||||
regex: '.+?',
|
||||
},
|
||||
]
|
||||
),
|
||||
method_sep: mergeTokens(
|
||||
// done
|
||||
addEOL(
|
||||
['whitespace', 'url.protocol_host', 'url.slash'],
|
||||
/(\s+)(https?:\/\/[^?\/,]+)(\/)/,
|
||||
'start',
|
||||
'url'
|
||||
),
|
||||
// done
|
||||
addEOL(['whitespace', 'variable.template'], /(\s+)(\${\w+})/, 'start', 'url'),
|
||||
// done
|
||||
addEOL(['whitespace', 'url.protocol_host'], /(\s+)(https?:\/\/[^?\/,]+)/, 'start', 'url'),
|
||||
// done
|
||||
addEOL(['whitespace', 'url.slash'], /(\s+)(\/)/, 'start', 'url'),
|
||||
// done
|
||||
addEOL(['whitespace'], /(\s+)/, 'start', 'url')
|
||||
),
|
||||
url: mergeTokens(
|
||||
// done
|
||||
addEOL(['variable.template'], /(\${\w+})/, 'start'),
|
||||
// TODO
|
||||
addEOL(['url.part'], /(_sql)/, 'start-sql', 'url-sql'),
|
||||
// done
|
||||
addEOL(['url.part'], /([^?\/,\s]+)/, 'start'),
|
||||
// done
|
||||
addEOL(['url.comma'], /(,)/, 'start'),
|
||||
// done
|
||||
addEOL(['url.slash'], /(\/)/, 'start'),
|
||||
// done
|
||||
addEOL(['url.questionmark'], /(\?)/, 'start', 'urlParams'),
|
||||
// done
|
||||
addEOL(['whitespace', 'comment.punctuation', 'comment.line'], /(\s+)(\/\/)(.*$)/, 'start')
|
||||
),
|
||||
urlParams: mergeTokens(
|
||||
// done
|
||||
addEOL(['url.param', 'url.equal', 'variable.template'], /([^&=]+)(=)(\${\w+})/, 'start'),
|
||||
// done
|
||||
addEOL(['url.param', 'url.equal', 'url.value'], /([^&=]+)(=)([^&]*)/, 'start'),
|
||||
// done
|
||||
addEOL(['url.param'], /([^&=]+)/, 'start'),
|
||||
// done
|
||||
addEOL(['url.amp'], /(&)/, 'start'),
|
||||
// done
|
||||
addEOL(['whitespace', 'comment.punctuation', 'comment.line'], /(\s+)(\/\/)(.*$)/, 'start')
|
||||
),
|
||||
// TODO
|
||||
'url-sql': mergeTokens(
|
||||
addEOL(['url.part'], /([^?\/,\s]+)/, 'start-sql'),
|
||||
addEOL(['url.comma'], /(,)/, 'start-sql'),
|
||||
addEOL(['url.slash'], /(\/)/, 'start-sql'),
|
||||
addEOL(['url.questionmark'], /(\?)/, 'start-sql', 'urlParams-sql')
|
||||
),
|
||||
// TODO
|
||||
'urlParams-sql': mergeTokens(
|
||||
addEOL(['url.param', 'url.equal', 'url.value'], /([^&=]+)(=)([^&]*)/, 'start-sql'),
|
||||
addEOL(['url.param'], /([^&=]+)/, 'start-sql'),
|
||||
addEOL(['url.amp'], /(&)/, 'start-sql')
|
||||
),
|
||||
/**
|
||||
* Each key in this.$rules considered to be a state in state machine. Regular expressions define the tokens for the current state, as well as the transitions into another state.
|
||||
* See for more details https://cloud9-sdk.readme.io/docs/highlighting-rules#section-defining-states
|
||||
* *
|
||||
* Define a state for comments, these comment rules then can be included in other states. E.g. in 'start' and 'json' states by including { include: 'comments' }
|
||||
* This will avoid duplicating the same rules in other states
|
||||
*/
|
||||
comments: [
|
||||
{
|
||||
// Capture a line comment, indicated by #
|
||||
// done
|
||||
token: ['comment.punctuation', 'comment.line'],
|
||||
regex: /(#)(.*$)/,
|
||||
},
|
||||
{
|
||||
// Begin capturing a block comment, indicated by /*
|
||||
// done
|
||||
token: 'comment.punctuation',
|
||||
regex: /\/\*/,
|
||||
push: [
|
||||
{
|
||||
// Finish capturing a block comment, indicated by */
|
||||
// done
|
||||
token: 'comment.punctuation',
|
||||
regex: /\*\//,
|
||||
next: 'pop',
|
||||
},
|
||||
{
|
||||
// done
|
||||
defaultToken: 'comment.block',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// Capture a line comment, indicated by //
|
||||
// done
|
||||
token: ['comment.punctuation', 'comment.line'],
|
||||
regex: /(\/\/)(.*$)/,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
addXJsonToRules(this, 'json');
|
||||
// Add comment rules to json rule set
|
||||
this.$rules.json.unshift({ include: 'comments' });
|
||||
|
||||
this.$rules.json.unshift({ token: 'variable.template', regex: /("\${\w+}")/ });
|
||||
|
||||
if (this instanceof InputHighlightRules) {
|
||||
this.normalizeRules();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +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 ace from 'brace';
|
||||
|
||||
import { OutputJsonHighlightRules } from './output_highlight_rules';
|
||||
|
||||
const JSONMode = ace.acequire('ace/mode/json').Mode;
|
||||
const MatchingBraceOutdent = ace.acequire('ace/mode/matching_brace_outdent').MatchingBraceOutdent;
|
||||
const CstyleBehaviour = ace.acequire('ace/mode/behaviour/cstyle').CstyleBehaviour;
|
||||
const CStyleFoldMode = ace.acequire('ace/mode/folding/cstyle').FoldMode;
|
||||
ace.acequire('ace/worker/worker_client');
|
||||
const AceTokenizer = ace.acequire('ace/tokenizer').Tokenizer;
|
||||
|
||||
export class Mode extends JSONMode {
|
||||
constructor() {
|
||||
super();
|
||||
this.$tokenizer = new AceTokenizer(new OutputJsonHighlightRules().getRules());
|
||||
this.$outdent = new MatchingBraceOutdent();
|
||||
this.$behaviour = new CstyleBehaviour();
|
||||
this.foldingRules = new CStyleFoldMode();
|
||||
}
|
||||
}
|
||||
|
||||
(function (this: Mode) {
|
||||
this.createWorker = function () {
|
||||
return null;
|
||||
};
|
||||
|
||||
this.$id = 'sense/mode/input';
|
||||
}).call(Mode.prototype);
|
|
@ -1,56 +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 { mapStatusCodeToBadge } from './output_highlight_rules';
|
||||
|
||||
describe('mapStatusCodeToBadge', () => {
|
||||
const testCases = [
|
||||
{
|
||||
description: 'treats 100 as as default',
|
||||
value: '# PUT test-index 100 Continue',
|
||||
badge: 'badge.badge--default',
|
||||
},
|
||||
{
|
||||
description: 'treats 200 as success',
|
||||
value: '# PUT test-index 200 OK',
|
||||
badge: 'badge.badge--success',
|
||||
},
|
||||
{
|
||||
description: 'treats 301 as primary',
|
||||
value: '# PUT test-index 301 Moved Permanently',
|
||||
badge: 'badge.badge--primary',
|
||||
},
|
||||
{
|
||||
description: 'treats 400 as warning',
|
||||
value: '# PUT test-index 404 Not Found',
|
||||
badge: 'badge.badge--warning',
|
||||
},
|
||||
{
|
||||
description: 'treats 502 as danger',
|
||||
value: '# PUT test-index 502 Bad Gateway',
|
||||
badge: 'badge.badge--danger',
|
||||
},
|
||||
{
|
||||
description: 'treats unexpected numbers as danger',
|
||||
value: '# PUT test-index 666 Demonic Invasion',
|
||||
badge: 'badge.badge--danger',
|
||||
},
|
||||
{
|
||||
description: 'treats no numbers as undefined',
|
||||
value: '# PUT test-index',
|
||||
badge: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach(({ description, value, badge }) => {
|
||||
test(description, () => {
|
||||
expect(mapStatusCodeToBadge(value)).toBe(badge);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,64 +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 ace from 'brace';
|
||||
import 'brace/mode/json';
|
||||
import { addXJsonToRules } from '@kbn/ace';
|
||||
|
||||
const JsonHighlightRules = ace.acequire('ace/mode/json_highlight_rules').JsonHighlightRules;
|
||||
|
||||
export const mapStatusCodeToBadge = (value?: string) => {
|
||||
const regExpMatchArray = value?.match(/\d+/);
|
||||
if (regExpMatchArray) {
|
||||
const status = parseInt(regExpMatchArray[0], 10);
|
||||
if (status <= 199) {
|
||||
return 'badge.badge--default';
|
||||
}
|
||||
if (status <= 299) {
|
||||
return 'badge.badge--success';
|
||||
}
|
||||
if (status <= 399) {
|
||||
return 'badge.badge--primary';
|
||||
}
|
||||
if (status <= 499) {
|
||||
return 'badge.badge--warning';
|
||||
}
|
||||
return 'badge.badge--danger';
|
||||
}
|
||||
};
|
||||
|
||||
export class OutputJsonHighlightRules extends JsonHighlightRules {
|
||||
constructor() {
|
||||
super();
|
||||
this.$rules = {};
|
||||
addXJsonToRules(this, 'start');
|
||||
this.$rules.start.unshift(
|
||||
{
|
||||
token: 'warning',
|
||||
regex: '#!.*$',
|
||||
},
|
||||
{
|
||||
token: 'comment',
|
||||
// match a comment starting with a hash at the start of the line
|
||||
// ignore status codes and status texts at the end of the line (e.g. # GET _search/foo 200, # GET _search/foo 200 OK)
|
||||
regex: /#(.*?)(?=[1-5][0-9][0-9]\s(?:[\sA-Za-z]+)|(?:[1-5][0-9][0-9])|$)/,
|
||||
},
|
||||
{
|
||||
token: mapStatusCodeToBadge,
|
||||
// match status codes and status texts at the end of the line (e.g. # GET _search/foo 200, # GET _search/foo 200 OK)
|
||||
// this rule allows us to highlight them with the corresponding badge color (e.g. 200 OK -> badge.badge--success)
|
||||
regex: /([1-5][0-9][0-9]\s?[\sA-Za-z]+$)/,
|
||||
}
|
||||
);
|
||||
|
||||
if (this instanceof OutputJsonHighlightRules) {
|
||||
this.normalizeRules();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +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 ace from 'brace';
|
||||
import { ScriptHighlightRules } from '@kbn/ace';
|
||||
|
||||
const TextMode = ace.acequire('ace/mode/text').Mode;
|
||||
const MatchingBraceOutdent = ace.acequire('ace/mode/matching_brace_outdent').MatchingBraceOutdent;
|
||||
const CstyleBehaviour = ace.acequire('ace/mode/behaviour/cstyle').CstyleBehaviour;
|
||||
const CStyleFoldMode = ace.acequire('ace/mode/folding/cstyle').FoldMode;
|
||||
ace.acequire('ace/tokenizer');
|
||||
|
||||
export class ScriptMode extends TextMode {
|
||||
constructor() {
|
||||
super();
|
||||
this.$outdent = new MatchingBraceOutdent();
|
||||
this.$behaviour = new CstyleBehaviour();
|
||||
this.foldingRules = new CStyleFoldMode();
|
||||
}
|
||||
}
|
||||
|
||||
(function (this: ScriptMode) {
|
||||
this.HighlightRules = ScriptHighlightRules;
|
||||
|
||||
this.getNextLineIndent = function (state: unknown, line: string, tab: string) {
|
||||
let indent = this.$getIndent(line);
|
||||
const match = line.match(/^.*[\{\[]\s*$/);
|
||||
if (match) {
|
||||
indent += tab;
|
||||
}
|
||||
|
||||
return indent;
|
||||
};
|
||||
|
||||
this.checkOutdent = function (state: unknown, line: string, input: string) {
|
||||
return this.$outdent.checkOutdent(line, input);
|
||||
};
|
||||
|
||||
this.autoOutdent = function (state: unknown, doc: string, row: string) {
|
||||
this.$outdent.autoOutdent(doc, row);
|
||||
};
|
||||
}).call(ScriptMode.prototype);
|
|
@ -1,10 +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".
|
||||
*/
|
||||
|
||||
export declare const workerModule: { id: string; src: string };
|
|
@ -1,15 +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 src from '!!raw-loader!./worker';
|
||||
|
||||
export const workerModule = {
|
||||
id: 'sense_editor/mode/worker',
|
||||
src,
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,91 +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 './legacy_core_editor.test.mocks';
|
||||
import $ from 'jquery';
|
||||
import RowParser from '../../../lib/row_parser';
|
||||
import ace from 'brace';
|
||||
import { createReadOnlyAceEditor } from './create_readonly';
|
||||
let output;
|
||||
const tokenIterator = ace.acequire('ace/token_iterator');
|
||||
|
||||
describe('Output Tokenization', () => {
|
||||
beforeEach(() => {
|
||||
output = createReadOnlyAceEditor(document.querySelector('#ConAppOutput'));
|
||||
$(output.container).show();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
$(output.container).hide();
|
||||
});
|
||||
|
||||
function tokensAsList() {
|
||||
const iter = new tokenIterator.TokenIterator(output.getSession(), 0, 0);
|
||||
const ret = [];
|
||||
let t = iter.getCurrentToken();
|
||||
const parser = new RowParser(output);
|
||||
if (parser.isEmptyToken(t)) {
|
||||
t = parser.nextNonEmptyToken(iter);
|
||||
}
|
||||
while (t) {
|
||||
ret.push({ value: t.value, type: t.type });
|
||||
t = parser.nextNonEmptyToken(iter);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let testCount = 0;
|
||||
|
||||
function tokenTest(tokenList, data) {
|
||||
if (data && typeof data !== 'string') {
|
||||
data = JSON.stringify(data, null, 3);
|
||||
}
|
||||
|
||||
test('Token test ' + testCount++, function (done) {
|
||||
output.update(data, function () {
|
||||
const tokens = tokensAsList();
|
||||
const normTokenList = [];
|
||||
for (let i = 0; i < tokenList.length; i++) {
|
||||
normTokenList.push({ type: tokenList[i++], value: tokenList[i] });
|
||||
}
|
||||
|
||||
expect(tokens).toEqual(normTokenList);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
tokenTest(
|
||||
['warning', '#! warning', 'comment', '# GET url', 'paren.lparen', '{', 'paren.rparen', '}'],
|
||||
'#! warning\n' + '# GET url\n' + '{}'
|
||||
);
|
||||
|
||||
tokenTest(
|
||||
[
|
||||
'comment',
|
||||
'# GET url',
|
||||
'paren.lparen',
|
||||
'{',
|
||||
'variable',
|
||||
'"f"',
|
||||
'punctuation.colon',
|
||||
':',
|
||||
'punctuation.start_triple_quote',
|
||||
'"""',
|
||||
'multi_string',
|
||||
'raw',
|
||||
'punctuation.end_triple_quote',
|
||||
'"""',
|
||||
'paren.rparen',
|
||||
'}',
|
||||
],
|
||||
'# GET url\n' + '{ "f": """raw""" }'
|
||||
);
|
||||
});
|
|
@ -1,27 +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 { get, throttle } from 'lodash';
|
||||
import type { Editor } from 'brace';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function (editor: Editor) {
|
||||
const resize = editor.resize;
|
||||
|
||||
const throttledResize = throttle(() => {
|
||||
resize.call(editor, false);
|
||||
|
||||
// Keep current top line in view when resizing to avoid losing user context
|
||||
const userRow = get(throttledResize, 'topRow', 0);
|
||||
if (userRow !== 0) {
|
||||
editor.renderer.scrollToLine(userRow, false, false, () => {});
|
||||
}
|
||||
}, 35);
|
||||
return throttledResize;
|
||||
}
|
|
@ -1,123 +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 ace from 'brace';
|
||||
|
||||
ace.define('ace/theme/sense-dark', ['require', 'exports', 'module'], function (require, exports) {
|
||||
exports.isDark = true;
|
||||
exports.cssClass = 'ace-sense-dark';
|
||||
exports.cssText =
|
||||
'.ace-sense-dark .ace_gutter {\
|
||||
background: #2e3236;\
|
||||
color: #bbbfc2;\
|
||||
}\
|
||||
.ace-sense-dark .ace_print-margin {\
|
||||
width: 1px;\
|
||||
background: #555651\
|
||||
}\
|
||||
.ace-sense-dark .ace_scroller {\
|
||||
background-color: #202328;\
|
||||
}\
|
||||
.ace-sense-dark .ace_content {\
|
||||
}\
|
||||
.ace-sense-dark .ace_text-layer {\
|
||||
color: #F8F8F2\
|
||||
}\
|
||||
.ace-sense-dark .ace_cursor {\
|
||||
border-left: 2px solid #F8F8F0\
|
||||
}\
|
||||
.ace-sense-dark .ace_overwrite-cursors .ace_cursor {\
|
||||
border-left: 0px;\
|
||||
border-bottom: 1px solid #F8F8F0\
|
||||
}\
|
||||
.ace-sense-dark .ace_marker-layer .ace_selection {\
|
||||
background: #222\
|
||||
}\
|
||||
.ace-sense-dark.ace_multiselect .ace_selection.ace_start {\
|
||||
box-shadow: 0 0 3px 0px #272822;\
|
||||
border-radius: 2px\
|
||||
}\
|
||||
.ace-sense-dark .ace_marker-layer .ace_step {\
|
||||
background: rgb(102, 82, 0)\
|
||||
}\
|
||||
.ace-sense-dark .ace_marker-layer .ace_bracket {\
|
||||
margin: -1px 0 0 -1px;\
|
||||
border: 1px solid #49483E\
|
||||
}\
|
||||
.ace-sense-dark .ace_marker-layer .ace_active-line {\
|
||||
background: #202020\
|
||||
}\
|
||||
.ace-sense-dark .ace_gutter-active-line {\
|
||||
background-color: #272727\
|
||||
}\
|
||||
.ace-sense-dark .ace_marker-layer .ace_selected-word {\
|
||||
border: 1px solid #49483E\
|
||||
}\
|
||||
.ace-sense-dark .ace_invisible {\
|
||||
color: #49483E\
|
||||
}\
|
||||
.ace-sense-dark .ace_entity.ace_name.ace_tag,\
|
||||
.ace-sense-dark .ace_keyword,\
|
||||
.ace-sense-dark .ace_meta,\
|
||||
.ace-sense-dark .ace_storage {\
|
||||
color: #F92672\
|
||||
}\
|
||||
.ace-sense-dark .ace_constant.ace_character,\
|
||||
.ace-sense-dark .ace_constant.ace_language,\
|
||||
.ace-sense-dark .ace_constant.ace_numeric,\
|
||||
.ace-sense-dark .ace_constant.ace_other {\
|
||||
color: #AE81FF\
|
||||
}\
|
||||
.ace-sense-dark .ace_invalid {\
|
||||
color: #F8F8F0;\
|
||||
background-color: #F92672\
|
||||
}\
|
||||
.ace-sense-dark .ace_invalid.ace_deprecated {\
|
||||
color: #F8F8F0;\
|
||||
background-color: #AE81FF\
|
||||
}\
|
||||
.ace-sense-dark .ace_support.ace_constant,\
|
||||
.ace-sense-dark .ace_support.ace_function {\
|
||||
color: #66D9EF\
|
||||
}\
|
||||
.ace-sense-dark .ace_fold {\
|
||||
background-color: #A6E22E;\
|
||||
border-color: #F8F8F2\
|
||||
}\
|
||||
.ace-sense-dark .ace_storage.ace_type,\
|
||||
.ace-sense-dark .ace_support.ace_class,\
|
||||
.ace-sense-dark .ace_support.ace_type {\
|
||||
font-style: italic;\
|
||||
color: #66D9EF\
|
||||
}\
|
||||
.ace-sense-dark .ace_entity.ace_name.ace_function,\
|
||||
.ace-sense-dark .ace_entity.ace_other.ace_attribute-name,\
|
||||
.ace-sense-dark .ace_variable {\
|
||||
color: #A6E22E\
|
||||
}\
|
||||
.ace-sense-dark .ace_variable.ace_parameter {\
|
||||
font-style: italic;\
|
||||
color: #FD971F\
|
||||
}\
|
||||
.ace-sense-dark .ace_string {\
|
||||
color: #E6DB74\
|
||||
}\
|
||||
.ace-sense-dark .ace_comment {\
|
||||
color: #629755\
|
||||
}\
|
||||
.ace-sense-dark .ace_markup.ace_underline {\
|
||||
text-decoration: underline\
|
||||
}\
|
||||
.ace-sense-dark .ace_indent-guide {\
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWNQ11D6z7Bq1ar/ABCKBG6g04U2AAAAAElFTkSuQmCC) right repeat-y\
|
||||
}';
|
||||
|
||||
const dom = require('ace/lib/dom');
|
||||
dom.importCssString(exports.cssText, exports.cssClass);
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
GET _search
|
||||
{
|
||||
"query": { "match_all": {} }
|
||||
}
|
||||
|
||||
#preceeding comment
|
||||
GET _stats?level=shards
|
||||
|
||||
#in between comment
|
||||
|
||||
PUT index_1/type1/1
|
||||
{
|
||||
"f": 1
|
||||
}
|
||||
|
||||
PUT index_1/type1/2
|
||||
{
|
||||
"f": 2
|
||||
}
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
GET index_1/type1/1/_source?_source_include=f
|
||||
|
||||
DELETE index_2
|
||||
|
||||
|
||||
POST /_sql?format=txt
|
||||
{
|
||||
"query": "SELECT prenom FROM claude_index WHERE prenom = 'claude' ",
|
||||
"fetch_size": 1
|
||||
}
|
||||
|
||||
GET <index_1-{now/d-2d}>,<index_1-{now/d-1d}>,<index_1-{now/d}>/_search?pretty
|
||||
|
||||
GET kbn:/api/spaces/space
|
|
@ -1,22 +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 { SenseEditor } from './sense_editor';
|
||||
import * as core from '../legacy_core_editor';
|
||||
|
||||
export function create(element: HTMLElement) {
|
||||
const coreEditor = core.create(element);
|
||||
const senseEditor = new SenseEditor(coreEditor);
|
||||
|
||||
/**
|
||||
* Init the editor
|
||||
*/
|
||||
senseEditor.highlightCurrentRequestsAndUpdateActionBar();
|
||||
return senseEditor;
|
||||
}
|
|
@ -1,194 +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".
|
||||
*/
|
||||
|
||||
function detectCURLinLine(line: string) {
|
||||
// returns true if text matches a curl request
|
||||
return line.match(/^\s*?curl\s+(-X[A-Z]+)?\s*['"]?.*?['"]?(\s*$|\s+?-d\s*?['"])/);
|
||||
}
|
||||
|
||||
export function detectCURL(text: string) {
|
||||
// returns true if text matches a curl request
|
||||
if (!text) return false;
|
||||
for (const line of text.split('\n')) {
|
||||
if (detectCURLinLine(line)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseCURL(text: string) {
|
||||
let state = 'NONE';
|
||||
const out = [];
|
||||
let body: string[] = [];
|
||||
let line = '';
|
||||
const lines = text.trim().split('\n');
|
||||
let matches;
|
||||
|
||||
const EmptyLine = /^\s*$/;
|
||||
const Comment = /^\s*(?:#|\/{2,})(.*)\n?$/;
|
||||
const ExecutionComment = /^\s*#!/;
|
||||
const ClosingSingleQuote = /^([^']*)'/;
|
||||
const ClosingDoubleQuote = /^((?:[^\\"]|\\.)*)"/;
|
||||
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;
|
||||
|
||||
const LooksLikeCurl = /^\s*curl\s+/;
|
||||
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;
|
||||
|
||||
const HasProtocol = /[\s"']https?:\/\//;
|
||||
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
|
||||
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
|
||||
const CurlData = /^.+\s(--data|-d)\s*/;
|
||||
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;
|
||||
|
||||
if (lines.length > 0 && ExecutionComment.test(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
function nextLine() {
|
||||
if (line.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (lines.length === 0) {
|
||||
return false;
|
||||
}
|
||||
line = lines.shift()!.replace(/[\r\n]+/g, '\n') + '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
function unescapeLastBodyEl() {
|
||||
const str = body.pop()!.replace(/\\([\\"'])/g, '$1');
|
||||
body.push(str);
|
||||
}
|
||||
|
||||
// Is the next char a single or double quote?
|
||||
// If so remove it
|
||||
function detectQuote() {
|
||||
if (line.substr(0, 1) === "'") {
|
||||
line = line.substr(1);
|
||||
state = 'SINGLE_QUOTE';
|
||||
} else if (line.substr(0, 1) === '"') {
|
||||
line = line.substr(1);
|
||||
state = 'DOUBLE_QUOTE';
|
||||
} else {
|
||||
state = 'UNQUOTED';
|
||||
}
|
||||
}
|
||||
|
||||
// Body is finished - append to output with final LF
|
||||
function addBodyToOut() {
|
||||
if (body.length > 0) {
|
||||
out.push(body.join(''));
|
||||
body = [];
|
||||
}
|
||||
state = 'LF';
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
// If the pattern matches, then the state is about to change,
|
||||
// so add the capture to the body and detect the next state
|
||||
// Otherwise add the whole line
|
||||
function consumeMatching(pattern: string | RegExp) {
|
||||
const result = line.match(pattern);
|
||||
if (result) {
|
||||
body.push(result[1]);
|
||||
line = line.substr(result[0].length);
|
||||
detectQuote();
|
||||
} else {
|
||||
body.push(line);
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
function parseCurlLine() {
|
||||
let verb = 'GET';
|
||||
let request = '';
|
||||
let result;
|
||||
if ((result = line.match(CurlVerb))) {
|
||||
verb = result[1];
|
||||
}
|
||||
|
||||
// JS regexen don't support possessive quantifiers, so
|
||||
// we need two distinct patterns
|
||||
const pattern = HasProtocol.test(line) ? CurlRequestWithProto : CurlRequestWithoutProto;
|
||||
|
||||
if ((result = line.match(pattern))) {
|
||||
request = result[1];
|
||||
}
|
||||
|
||||
out.push(verb + ' /' + request + '\n');
|
||||
|
||||
if ((result = line.match(CurlData))) {
|
||||
line = line.substr(result[0].length);
|
||||
detectQuote();
|
||||
if (EmptyLine.test(line)) {
|
||||
line = '';
|
||||
}
|
||||
} else {
|
||||
state = 'NONE';
|
||||
line = '';
|
||||
out.push('');
|
||||
}
|
||||
}
|
||||
|
||||
while (nextLine()) {
|
||||
if (state === 'SINGLE_QUOTE') {
|
||||
consumeMatching(ClosingSingleQuote);
|
||||
} else if (state === 'DOUBLE_QUOTE') {
|
||||
consumeMatching(ClosingDoubleQuote);
|
||||
unescapeLastBodyEl();
|
||||
} else if (state === 'UNQUOTED') {
|
||||
consumeMatching(EscapedQuotes);
|
||||
if (body.length) {
|
||||
unescapeLastBodyEl();
|
||||
}
|
||||
if (state === 'UNQUOTED') {
|
||||
addBodyToOut();
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
// the BODY state (used to match the body of a Sense request)
|
||||
// can be terminated early if it encounters
|
||||
// a comment or an empty line
|
||||
else if (state === 'BODY') {
|
||||
if (Comment.test(line) || EmptyLine.test(line)) {
|
||||
addBodyToOut();
|
||||
} else {
|
||||
body.push(line);
|
||||
line = '';
|
||||
}
|
||||
} else if (EmptyLine.test(line)) {
|
||||
if (state !== 'LF') {
|
||||
out.push('\n');
|
||||
state = 'LF';
|
||||
}
|
||||
line = '';
|
||||
} else if ((matches = line.match(Comment))) {
|
||||
out.push('#' + matches[1] + '\n');
|
||||
state = 'NONE';
|
||||
line = '';
|
||||
} else if (LooksLikeCurl.test(line)) {
|
||||
parseCurlLine();
|
||||
} else if ((matches = line.match(SenseLine))) {
|
||||
out.push(matches[1] + ' /' + matches[2] + '\n');
|
||||
line = '';
|
||||
state = 'BODY';
|
||||
}
|
||||
|
||||
// Nothing else matches, so output with a prefix of ### for debugging purposes
|
||||
else {
|
||||
out.push('### ' + line);
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
addBodyToOut();
|
||||
return out.join('').trim();
|
||||
}
|
|
@ -1,14 +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".
|
||||
*/
|
||||
|
||||
export * from './create';
|
||||
export * from '../legacy_core_editor/create_readonly';
|
||||
export { MODE } from '../../../lib/row_parser';
|
||||
export { SenseEditor } from './sense_editor';
|
||||
export { getEndpointFromPosition } from '../../../lib/autocomplete/get_endpoint_from_position';
|
File diff suppressed because it is too large
Load diff
|
@ -1,641 +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 './sense_editor.test.mocks';
|
||||
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { create } from './create';
|
||||
import { XJson } from '@kbn/es-ui-shared-plugin/public';
|
||||
import editorInput1 from './__fixtures__/editor_input1.txt';
|
||||
import { setStorage, createStorage } from '../../../services';
|
||||
|
||||
const { collapseLiteralStrings } = XJson;
|
||||
|
||||
describe('Editor', () => {
|
||||
let input;
|
||||
let oldUrl;
|
||||
let olldWindow;
|
||||
let storage;
|
||||
|
||||
beforeEach(function () {
|
||||
// Set up our document body
|
||||
document.body.innerHTML = `<div>
|
||||
<div id="ConAppEditor" />
|
||||
<div id="ConAppEditorActions" />
|
||||
<div id="ConCopyAsCurl" />
|
||||
</div>`;
|
||||
|
||||
input = create(document.querySelector('#ConAppEditor'));
|
||||
$(input.getCoreEditor().getContainer()).show();
|
||||
input.autocomplete._test.removeChangeListener();
|
||||
oldUrl = global.URL;
|
||||
olldWindow = { ...global.window };
|
||||
global.URL = URL;
|
||||
Object.defineProperty(global, 'window', {
|
||||
value: Object.create(window),
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
origin: 'http://localhost:5620',
|
||||
},
|
||||
});
|
||||
storage = createStorage({
|
||||
engine: global.window.localStorage,
|
||||
prefix: 'console_test',
|
||||
});
|
||||
setStorage(storage);
|
||||
});
|
||||
afterEach(function () {
|
||||
global.URL = oldUrl;
|
||||
global.window = olldWindow;
|
||||
$(input.getCoreEditor().getContainer()).hide();
|
||||
input.autocomplete._test.addChangeListener();
|
||||
setStorage(null);
|
||||
});
|
||||
|
||||
let testCount = 0;
|
||||
|
||||
const callWithEditorMethod = (editorMethod, fn) => async (done) => {
|
||||
const results = await input[editorMethod]();
|
||||
fn(results, done);
|
||||
};
|
||||
|
||||
function utilsTest(name, prefix, data, testToRun) {
|
||||
const id = testCount++;
|
||||
if (typeof data === 'function') {
|
||||
testToRun = data;
|
||||
data = null;
|
||||
}
|
||||
if (data && typeof data !== 'string') {
|
||||
data = JSON.stringify(data, null, 3);
|
||||
}
|
||||
if (data) {
|
||||
if (prefix) {
|
||||
data = prefix + '\n' + data;
|
||||
}
|
||||
} else {
|
||||
data = prefix;
|
||||
}
|
||||
|
||||
test('Utils test ' + id + ' : ' + name, function (done) {
|
||||
input.update(data, true).then(() => {
|
||||
testToRun(done);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function compareRequest(requests, expected) {
|
||||
if (!Array.isArray(requests)) {
|
||||
requests = [requests];
|
||||
expected = [expected];
|
||||
}
|
||||
|
||||
_.each(requests, function (r) {
|
||||
delete r.range;
|
||||
});
|
||||
expect(requests).toEqual(expected);
|
||||
}
|
||||
|
||||
const simpleRequest = {
|
||||
prefix: 'POST _search',
|
||||
data: ['{', ' "query": { "match_all": {} }', '}'].join('\n'),
|
||||
};
|
||||
|
||||
const singleLineRequest = {
|
||||
prefix: 'POST _search',
|
||||
data: '{ "query": { "match_all": {} } }',
|
||||
};
|
||||
|
||||
const getRequestNoData = {
|
||||
prefix: 'GET _stats',
|
||||
};
|
||||
|
||||
const multiDocRequest = {
|
||||
prefix: 'POST _bulk',
|
||||
data_as_array: ['{ "index": { "_index": "index", "_type":"type" } }', '{ "field": 1 }'],
|
||||
};
|
||||
multiDocRequest.data = multiDocRequest.data_as_array.join('\n');
|
||||
|
||||
utilsTest(
|
||||
'simple request range',
|
||||
simpleRequest.prefix,
|
||||
simpleRequest.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
compareRequest(range, {
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 4, column: 2 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request data',
|
||||
simpleRequest.prefix,
|
||||
simpleRequest.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: '_search',
|
||||
data: [simpleRequest.data],
|
||||
};
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request range, prefixed with spaces',
|
||||
' ' + simpleRequest.prefix,
|
||||
simpleRequest.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
expect(range).toEqual({
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 4, column: 2 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request data, prefixed with spaces',
|
||||
' ' + simpleRequest.prefix,
|
||||
simpleRequest.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: '_search',
|
||||
data: [simpleRequest.data],
|
||||
};
|
||||
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request range, suffixed with spaces',
|
||||
simpleRequest.prefix + ' ',
|
||||
simpleRequest.data + ' ',
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
compareRequest(range, {
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 4, column: 2 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request data, suffixed with spaces',
|
||||
simpleRequest.prefix + ' ',
|
||||
simpleRequest.data + ' ',
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: '_search',
|
||||
data: [simpleRequest.data],
|
||||
};
|
||||
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'single line request range',
|
||||
singleLineRequest.prefix,
|
||||
singleLineRequest.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
compareRequest(range, {
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 2, column: 33 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'full url: single line request data',
|
||||
'POST https://somehost/_search',
|
||||
singleLineRequest.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: 'https://somehost/_search',
|
||||
data: [singleLineRequest.data],
|
||||
};
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'request with no data followed by a new line',
|
||||
getRequestNoData.prefix,
|
||||
'\n',
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
compareRequest(range, {
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 1, column: 11 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'request with no data followed by a new line (data)',
|
||||
getRequestNoData.prefix,
|
||||
'\n',
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'GET',
|
||||
url: '_stats',
|
||||
data: [],
|
||||
};
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'request with no data',
|
||||
getRequestNoData.prefix,
|
||||
getRequestNoData.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
expect(range).toEqual({
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 1, column: 11 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'request with no data (data)',
|
||||
getRequestNoData.prefix,
|
||||
getRequestNoData.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'GET',
|
||||
url: '_stats',
|
||||
data: [],
|
||||
};
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'multi doc request range',
|
||||
multiDocRequest.prefix,
|
||||
multiDocRequest.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
expect(range).toEqual({
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 3, column: 15 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'multi doc request data',
|
||||
multiDocRequest.prefix,
|
||||
multiDocRequest.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: '_bulk',
|
||||
data: multiDocRequest.data_as_array,
|
||||
};
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
const scriptRequest = {
|
||||
prefix: 'POST _search',
|
||||
data: ['{', ' "query": { "script": """', ' some script ', ' """}', '}'].join('\n'),
|
||||
};
|
||||
|
||||
utilsTest(
|
||||
'script request range',
|
||||
scriptRequest.prefix,
|
||||
scriptRequest.data,
|
||||
callWithEditorMethod('getRequestRange', (range, done) => {
|
||||
compareRequest(range, {
|
||||
start: { lineNumber: 1, column: 1 },
|
||||
end: { lineNumber: 6, column: 2 },
|
||||
});
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
utilsTest(
|
||||
'simple request data',
|
||||
simpleRequest.prefix,
|
||||
simpleRequest.data,
|
||||
callWithEditorMethod('getRequest', (request, done) => {
|
||||
const expected = {
|
||||
method: 'POST',
|
||||
url: '_search',
|
||||
data: [collapseLiteralStrings(simpleRequest.data)],
|
||||
};
|
||||
|
||||
compareRequest(request, expected);
|
||||
done();
|
||||
})
|
||||
);
|
||||
|
||||
function multiReqTest(name, editorInput, range, expected) {
|
||||
utilsTest('multi request select - ' + name, editorInput, async function (done) {
|
||||
const requests = await input.getRequestsInRange(range, false);
|
||||
// convert to format returned by request.
|
||||
_.each(expected, function (req) {
|
||||
req.data = req.data == null ? [] : [JSON.stringify(req.data, null, 2)];
|
||||
});
|
||||
|
||||
compareRequest(requests, expected);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
multiReqTest(
|
||||
'mid body to mid body',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 13 }, end: { lineNumber: 18 } },
|
||||
[
|
||||
{
|
||||
method: 'PUT',
|
||||
url: 'index_1/type1/1',
|
||||
data: {
|
||||
f: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
url: 'index_1/type1/2',
|
||||
data: {
|
||||
f: 2,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'single request start to end',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 11 }, end: { lineNumber: 14 } },
|
||||
[
|
||||
{
|
||||
method: 'PUT',
|
||||
url: 'index_1/type1/1',
|
||||
data: {
|
||||
f: 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'start to end, with comment',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 7 }, end: { lineNumber: 14 } },
|
||||
[
|
||||
{
|
||||
method: 'GET',
|
||||
url: '_stats?level=shards',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
url: 'index_1/type1/1',
|
||||
data: {
|
||||
f: 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'before start to after end, with comments',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 5 }, end: { lineNumber: 15 } },
|
||||
[
|
||||
{
|
||||
method: 'GET',
|
||||
url: '_stats?level=shards',
|
||||
data: null,
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
url: 'index_1/type1/1',
|
||||
data: {
|
||||
f: 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'between requests',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 22 }, end: { lineNumber: 23 } },
|
||||
[]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'between requests - with comment',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 21 }, end: { lineNumber: 23 } },
|
||||
[]
|
||||
);
|
||||
|
||||
multiReqTest(
|
||||
'between requests - before comment',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 20 }, end: { lineNumber: 23 } },
|
||||
[]
|
||||
);
|
||||
|
||||
function multiReqCopyAsCurlTest(name, editorInput, range, expected) {
|
||||
utilsTest('multi request copy as curl - ' + name, editorInput, async function (done) {
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', range);
|
||||
expect(curl).toEqual(expected);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
multiReqCopyAsCurlTest(
|
||||
'start to end, with comment',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 7 }, end: { lineNumber: 14 } },
|
||||
`
|
||||
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 "kbn-xsrf: reporting" -H "Content-Type: application/json" -d'
|
||||
{
|
||||
"f": 1
|
||||
}'`.trim()
|
||||
);
|
||||
|
||||
multiReqCopyAsCurlTest(
|
||||
'with single quotes',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 29 }, end: { lineNumber: 33 } },
|
||||
`
|
||||
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
|
||||
}'`.trim()
|
||||
);
|
||||
|
||||
multiReqCopyAsCurlTest(
|
||||
'with date math index',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 35 }, end: { lineNumber: 35 } },
|
||||
`
|
||||
curl -XGET "http://localhost:9200/%3Cindex_1-%7Bnow%2Fd-2d%7D%3E%2C%3Cindex_1-%7Bnow%2Fd-1d%7D%3E%2C%3Cindex_1-%7Bnow%2Fd%7D%3E%2F_search?pretty" -H "kbn-xsrf: reporting"`.trim()
|
||||
);
|
||||
|
||||
multiReqCopyAsCurlTest(
|
||||
'with Kibana API request',
|
||||
editorInput1,
|
||||
{ start: { lineNumber: 37 }, end: { lineNumber: 37 } },
|
||||
`
|
||||
curl -XGET "http://localhost:5620/api/spaces/space" -H \"kbn-xsrf: reporting\"`.trim()
|
||||
);
|
||||
|
||||
describe('getRequestsAsCURL', () => {
|
||||
it('should return empty string if no requests', async () => {
|
||||
input?.getCoreEditor().setValue('', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 1 },
|
||||
});
|
||||
expect(curl).toEqual('');
|
||||
});
|
||||
|
||||
it('should replace variables in the URL', async () => {
|
||||
storage.set('variables', [{ name: 'exampleVariableA', value: 'valueA' }]);
|
||||
input?.getCoreEditor().setValue('GET ${exampleVariableA}', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 1 },
|
||||
});
|
||||
expect(curl).toContain('valueA');
|
||||
});
|
||||
|
||||
it('should replace variables in the body', async () => {
|
||||
storage.set('variables', [{ name: 'exampleVariableB', value: 'valueB' }]);
|
||||
console.log(storage.get('variables'));
|
||||
input
|
||||
?.getCoreEditor()
|
||||
.setValue('GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableB}": ""\n\t}\n}', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 6 },
|
||||
});
|
||||
expect(curl).toContain('valueB');
|
||||
});
|
||||
|
||||
it('should strip comments in the URL', async () => {
|
||||
input?.getCoreEditor().setValue('GET _search // comment', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 6 },
|
||||
});
|
||||
expect(curl).not.toContain('comment');
|
||||
});
|
||||
|
||||
it('should strip comments in the body', async () => {
|
||||
input
|
||||
?.getCoreEditor()
|
||||
.setValue('{\n\t"query": {\n\t\t"match_all": {} // comment \n\t}\n}', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 8 },
|
||||
});
|
||||
console.log('curl', curl);
|
||||
expect(curl).not.toContain('comment');
|
||||
});
|
||||
|
||||
it('should strip multi-line comments in the body', async () => {
|
||||
input
|
||||
?.getCoreEditor()
|
||||
.setValue('{\n\t"query": {\n\t\t"match_all": {} /* comment */\n\t}\n}', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 8 },
|
||||
});
|
||||
console.log('curl', curl);
|
||||
expect(curl).not.toContain('comment');
|
||||
});
|
||||
|
||||
it('should replace multiple variables in the URL', async () => {
|
||||
storage.set('variables', [
|
||||
{ name: 'exampleVariableA', value: 'valueA' },
|
||||
{ name: 'exampleVariableB', value: 'valueB' },
|
||||
]);
|
||||
input?.getCoreEditor().setValue('GET ${exampleVariableA}/${exampleVariableB}', false);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 1 },
|
||||
});
|
||||
expect(curl).toContain('valueA');
|
||||
expect(curl).toContain('valueB');
|
||||
});
|
||||
|
||||
it('should replace multiple variables in the body', async () => {
|
||||
storage.set('variables', [
|
||||
{ name: 'exampleVariableA', value: 'valueA' },
|
||||
{ name: 'exampleVariableB', value: 'valueB' },
|
||||
]);
|
||||
input
|
||||
?.getCoreEditor()
|
||||
.setValue(
|
||||
'GET _search\n{\t\t"query": {\n\t\t\t"${exampleVariableA}": "${exampleVariableB}"\n\t}\n}',
|
||||
false
|
||||
);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 6 },
|
||||
});
|
||||
expect(curl).toContain('valueA');
|
||||
expect(curl).toContain('valueB');
|
||||
});
|
||||
|
||||
it('should replace variables in bulk request', async () => {
|
||||
storage.set('variables', [
|
||||
{ name: 'exampleVariableA', value: 'valueA' },
|
||||
{ name: 'exampleVariableB', value: 'valueB' },
|
||||
]);
|
||||
input
|
||||
?.getCoreEditor()
|
||||
.setValue(
|
||||
'POST _bulk\n{"index": {"_id": "0"}}\n{"field" : "${exampleVariableA}"}\n{"index": {"_id": "1"}}\n{"field" : "${exampleVariableB}"}\n',
|
||||
false
|
||||
);
|
||||
const curl = await input.getRequestsAsCURL('http://localhost:9200', {
|
||||
start: { lineNumber: 1 },
|
||||
end: { lineNumber: 4 },
|
||||
});
|
||||
expect(curl).toContain('valueA');
|
||||
expect(curl).toContain('valueB');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,20 +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".
|
||||
*/
|
||||
|
||||
/* eslint no-undef: 0 */
|
||||
|
||||
import '../legacy_core_editor/legacy_core_editor.test.mocks';
|
||||
|
||||
import jQuery from 'jquery';
|
||||
jest.spyOn(jQuery, 'ajax').mockImplementation(
|
||||
() =>
|
||||
new Promise(() => {
|
||||
// never resolve
|
||||
}) as any
|
||||
);
|
|
@ -1,534 +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 _ from 'lodash';
|
||||
import { parse } from 'hjson';
|
||||
import { XJson } from '@kbn/es-ui-shared-plugin/public';
|
||||
|
||||
import RowParser from '../../../lib/row_parser';
|
||||
import * as utils from '../../../lib/utils';
|
||||
import { constructUrl } from '../../../lib/es/es';
|
||||
|
||||
import { CoreEditor, Position, Range } from '../../../types';
|
||||
import { createTokenIterator } from '../../factories';
|
||||
import createAutocompleter from '../../../lib/autocomplete/autocomplete';
|
||||
import { getStorage, StorageKeys } from '../../../services';
|
||||
import { DEFAULT_VARIABLES } from '../../../../common/constants';
|
||||
|
||||
const { collapseLiteralStrings } = XJson;
|
||||
|
||||
export class SenseEditor {
|
||||
currentReqRange: (Range & { markerRef: unknown }) | null;
|
||||
parser: RowParser;
|
||||
|
||||
private readonly autocomplete: ReturnType<typeof createAutocompleter>;
|
||||
|
||||
constructor(private readonly coreEditor: CoreEditor) {
|
||||
this.currentReqRange = null;
|
||||
this.parser = new RowParser(this.coreEditor);
|
||||
this.autocomplete = createAutocompleter({
|
||||
coreEditor,
|
||||
parser: this.parser,
|
||||
});
|
||||
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
|
||||
this.coreEditor.on(
|
||||
'tokenizerUpdate',
|
||||
this.highlightCurrentRequestsAndUpdateActionBar.bind(this)
|
||||
);
|
||||
this.coreEditor.on('changeCursor', this.highlightCurrentRequestsAndUpdateActionBar.bind(this));
|
||||
this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this));
|
||||
}
|
||||
|
||||
prevRequestStart = (rowOrPos?: number | Position): Position => {
|
||||
let curRow: number;
|
||||
|
||||
if (rowOrPos == null) {
|
||||
curRow = this.coreEditor.getCurrentPosition().lineNumber;
|
||||
} else if (_.isObject(rowOrPos)) {
|
||||
curRow = (rowOrPos as Position).lineNumber;
|
||||
} else {
|
||||
curRow = rowOrPos as number;
|
||||
}
|
||||
|
||||
while (curRow > 0 && !this.parser.isStartRequestRow(curRow, this.coreEditor)) curRow--;
|
||||
|
||||
return {
|
||||
lineNumber: curRow,
|
||||
column: 1,
|
||||
};
|
||||
};
|
||||
|
||||
nextRequestStart = (rowOrPos?: number | Position) => {
|
||||
let curRow: number;
|
||||
if (rowOrPos == null) {
|
||||
curRow = this.coreEditor.getCurrentPosition().lineNumber;
|
||||
} else if (_.isObject(rowOrPos)) {
|
||||
curRow = (rowOrPos as Position).lineNumber;
|
||||
} else {
|
||||
curRow = rowOrPos as number;
|
||||
}
|
||||
const maxLines = this.coreEditor.getLineCount();
|
||||
for (; curRow < maxLines - 1; curRow++) {
|
||||
if (this.parser.isStartRequestRow(curRow, this.coreEditor)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
row: curRow,
|
||||
column: 0,
|
||||
};
|
||||
};
|
||||
|
||||
autoIndent = _.debounce(async () => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
const reqRange = await this.getRequestRange();
|
||||
if (!reqRange) {
|
||||
return;
|
||||
}
|
||||
const parsedReq = await this.getRequest();
|
||||
|
||||
if (!parsedReq) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedReq.data.some((doc) => utils.hasComments(doc))) {
|
||||
/**
|
||||
* Comments require different approach for indentation and do not have condensed format
|
||||
* We need to delegate indentation logic to coreEditor since it has access to session and other methods used for formatting and indenting the comments
|
||||
*/
|
||||
this.coreEditor.autoIndent(parsedReq.range);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedReq.data && parsedReq.data.length > 0) {
|
||||
let indent = parsedReq.data.length === 1; // unindent multi docs by default
|
||||
let formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent);
|
||||
if (!formattedData.changed) {
|
||||
// toggle.
|
||||
indent = !indent;
|
||||
formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent);
|
||||
}
|
||||
parsedReq.data = formattedData.data;
|
||||
|
||||
this.replaceRequestRange(parsedReq, reqRange);
|
||||
}
|
||||
}, 25);
|
||||
|
||||
update = async (data: string, reTokenizeAll = false) => {
|
||||
return this.coreEditor.setValue(data, reTokenizeAll);
|
||||
};
|
||||
|
||||
replaceRequestRange = (
|
||||
newRequest: { method: string; url: string; data: string | string[] },
|
||||
requestRange: Range
|
||||
) => {
|
||||
const text = utils.textFromRequest(newRequest);
|
||||
if (requestRange) {
|
||||
this.coreEditor.replaceRange(requestRange, text);
|
||||
} else {
|
||||
// just insert where we are
|
||||
this.coreEditor.insert(this.coreEditor.getCurrentPosition(), text);
|
||||
}
|
||||
};
|
||||
|
||||
getRequestRange = async (lineNumber?: number): Promise<Range | null> => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
|
||||
if (this.parser.isInBetweenRequestsRow(lineNumber)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const reqStart = this.prevRequestStart(lineNumber);
|
||||
const reqEnd = this.nextRequestEnd(reqStart);
|
||||
|
||||
return {
|
||||
start: {
|
||||
...reqStart,
|
||||
},
|
||||
end: {
|
||||
...reqEnd,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
expandRangeToRequestEdges = async (
|
||||
range = this.coreEditor.getSelectionRange()
|
||||
): Promise<Range | null> => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
|
||||
let startLineNumber = range.start.lineNumber;
|
||||
let endLineNumber = range.end.lineNumber;
|
||||
const maxLine = Math.max(1, this.coreEditor.getLineCount());
|
||||
|
||||
if (this.parser.isInBetweenRequestsRow(startLineNumber)) {
|
||||
/* Do nothing... */
|
||||
} else {
|
||||
for (; startLineNumber >= 1; startLineNumber--) {
|
||||
if (this.parser.isStartRequestRow(startLineNumber)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startLineNumber < 1 || startLineNumber > endLineNumber) {
|
||||
return null;
|
||||
}
|
||||
// move end row to the previous request end if between requests, otherwise walk forward
|
||||
if (this.parser.isInBetweenRequestsRow(endLineNumber)) {
|
||||
for (; endLineNumber >= startLineNumber; endLineNumber--) {
|
||||
if (this.parser.isEndRequestRow(endLineNumber)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (; endLineNumber <= maxLine; endLineNumber++) {
|
||||
if (this.parser.isEndRequestRow(endLineNumber)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endLineNumber < startLineNumber || endLineNumber > maxLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const endColumn =
|
||||
(this.coreEditor.getLineValue(endLineNumber) || '').replace(/\s+$/, '').length + 1;
|
||||
return {
|
||||
start: {
|
||||
lineNumber: startLineNumber,
|
||||
column: 1,
|
||||
},
|
||||
end: {
|
||||
lineNumber: endLineNumber,
|
||||
column: endColumn,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
getRequestInRange = async (range?: Range) => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
const request: {
|
||||
method: string;
|
||||
data: string[];
|
||||
url: string;
|
||||
range: Range;
|
||||
} = {
|
||||
method: '',
|
||||
data: [],
|
||||
url: '',
|
||||
range,
|
||||
};
|
||||
|
||||
const pos = range.start;
|
||||
const tokenIter = createTokenIterator({ editor: this.coreEditor, position: pos });
|
||||
let t = tokenIter.getCurrentToken();
|
||||
if (this.parser.isEmptyToken(t)) {
|
||||
// if the row starts with some spaces, skip them.
|
||||
t = this.parser.nextNonEmptyToken(tokenIter);
|
||||
}
|
||||
if (t == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
request.method = t.value;
|
||||
t = this.parser.nextNonEmptyToken(tokenIter);
|
||||
|
||||
if (!t || t.type === 'method') {
|
||||
return null;
|
||||
}
|
||||
|
||||
request.url = '';
|
||||
|
||||
while (t && t.type && (t.type.indexOf('url') === 0 || t.type === 'variable.template')) {
|
||||
request.url += t.value;
|
||||
t = tokenIter.stepForward();
|
||||
}
|
||||
if (this.parser.isEmptyToken(t)) {
|
||||
// if the url row ends with some spaces, skip them.
|
||||
t = this.parser.nextNonEmptyToken(tokenIter);
|
||||
}
|
||||
|
||||
// If the url row ends with a comment, skip it
|
||||
while (this.parser.isCommentToken(t)) {
|
||||
t = tokenIter.stepForward();
|
||||
}
|
||||
|
||||
let bodyStartLineNumber = (t ? 0 : 1) + tokenIter.getCurrentPosition().lineNumber; // artificially increase end of docs.
|
||||
let dataEndPos: Position;
|
||||
while (
|
||||
bodyStartLineNumber < range.end.lineNumber ||
|
||||
(bodyStartLineNumber === range.end.lineNumber && 1 < range.end.column)
|
||||
) {
|
||||
dataEndPos = this.nextDataDocEnd({
|
||||
lineNumber: bodyStartLineNumber,
|
||||
column: 1,
|
||||
});
|
||||
const bodyRange: Range = {
|
||||
start: {
|
||||
lineNumber: bodyStartLineNumber,
|
||||
column: 1,
|
||||
},
|
||||
end: dataEndPos,
|
||||
};
|
||||
const data = this.coreEditor.getValueInRange(bodyRange)!;
|
||||
request.data.push(data.trim());
|
||||
bodyStartLineNumber = dataEndPos.lineNumber + 1;
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
getRequestsInRange = async (
|
||||
range = this.coreEditor.getSelectionRange(),
|
||||
includeNonRequestBlocks = false
|
||||
): Promise<any[]> => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
if (!range) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const expandedRange = await this.expandRangeToRequestEdges(range);
|
||||
if (!expandedRange) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const requests: unknown[] = [];
|
||||
|
||||
let rangeStartCursor = expandedRange.start.lineNumber;
|
||||
const endLineNumber = expandedRange.end.lineNumber;
|
||||
|
||||
// move to the next request start (during the second iterations this may not be exactly on a request
|
||||
let currentLineNumber = expandedRange.start.lineNumber;
|
||||
|
||||
const flushNonRequestBlock = () => {
|
||||
if (includeNonRequestBlocks) {
|
||||
const nonRequestPrefixBlock = this.coreEditor
|
||||
.getLines(rangeStartCursor, currentLineNumber - 1)
|
||||
.join('\n');
|
||||
if (nonRequestPrefixBlock) {
|
||||
requests.push(nonRequestPrefixBlock);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (currentLineNumber <= endLineNumber) {
|
||||
if (this.parser.isStartRequestRow(currentLineNumber)) {
|
||||
flushNonRequestBlock();
|
||||
const request = await this.getRequest(currentLineNumber);
|
||||
if (!request) {
|
||||
// Something has probably gone wrong.
|
||||
return requests;
|
||||
} else {
|
||||
requests.push(request);
|
||||
rangeStartCursor = currentLineNumber = request.range.end.lineNumber + 1;
|
||||
}
|
||||
} else {
|
||||
++currentLineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
flushNonRequestBlock();
|
||||
|
||||
return requests;
|
||||
};
|
||||
|
||||
getRequest = async (row?: number) => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
if (this.parser.isInBetweenRequestsRow(row)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const range = await this.getRequestRange(row);
|
||||
return this.getRequestInRange(range!);
|
||||
};
|
||||
|
||||
moveToPreviousRequestEdge = async () => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
const pos = this.coreEditor.getCurrentPosition();
|
||||
for (
|
||||
pos.lineNumber--;
|
||||
pos.lineNumber > 1 && !this.parser.isRequestEdge(pos.lineNumber);
|
||||
pos.lineNumber--
|
||||
) {
|
||||
// loop for side effects
|
||||
}
|
||||
this.coreEditor.moveCursorToPosition({
|
||||
lineNumber: pos.lineNumber,
|
||||
column: 1,
|
||||
});
|
||||
};
|
||||
|
||||
moveToNextRequestEdge = async (moveOnlyIfNotOnEdge: boolean) => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
const pos = this.coreEditor.getCurrentPosition();
|
||||
const maxRow = this.coreEditor.getLineCount();
|
||||
if (!moveOnlyIfNotOnEdge) {
|
||||
pos.lineNumber++;
|
||||
}
|
||||
for (
|
||||
;
|
||||
pos.lineNumber < maxRow && !this.parser.isRequestEdge(pos.lineNumber);
|
||||
pos.lineNumber++
|
||||
) {
|
||||
// loop for side effects
|
||||
}
|
||||
this.coreEditor.moveCursorToPosition({
|
||||
lineNumber: pos.lineNumber,
|
||||
column: 1,
|
||||
});
|
||||
};
|
||||
|
||||
nextRequestEnd = (pos: Position): Position => {
|
||||
pos = pos || this.coreEditor.getCurrentPosition();
|
||||
const maxLines = this.coreEditor.getLineCount();
|
||||
let curLineNumber = pos.lineNumber;
|
||||
for (; curLineNumber <= maxLines; ++curLineNumber) {
|
||||
const curRowMode = this.parser.getRowParseMode(curLineNumber);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const column =
|
||||
(this.coreEditor.getLineValue(curLineNumber) || '').replace(/\s+$/, '').length + 1;
|
||||
|
||||
return {
|
||||
lineNumber: curLineNumber,
|
||||
column,
|
||||
};
|
||||
};
|
||||
|
||||
nextDataDocEnd = (pos: Position): Position => {
|
||||
pos = pos || this.coreEditor.getCurrentPosition();
|
||||
let curLineNumber = pos.lineNumber;
|
||||
const maxLines = this.coreEditor.getLineCount();
|
||||
for (; curLineNumber < maxLines; curLineNumber++) {
|
||||
const curRowMode = this.parser.getRowParseMode(curLineNumber);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if ((curRowMode & this.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) {
|
||||
break;
|
||||
}
|
||||
// eslint-disable-next-line no-bitwise
|
||||
if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const column =
|
||||
(this.coreEditor.getLineValue(curLineNumber) || '').length +
|
||||
1; /* Range goes to 1 after last char */
|
||||
|
||||
return {
|
||||
lineNumber: curLineNumber,
|
||||
column,
|
||||
};
|
||||
};
|
||||
|
||||
highlightCurrentRequestsAndUpdateActionBar = _.debounce(async () => {
|
||||
await this.coreEditor.waitForLatestTokens();
|
||||
const expandedRange = await this.expandRangeToRequestEdges();
|
||||
if (expandedRange === null && this.currentReqRange === null) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
expandedRange !== null &&
|
||||
this.currentReqRange !== null &&
|
||||
expandedRange.start.lineNumber === this.currentReqRange.start.lineNumber &&
|
||||
expandedRange.end.lineNumber === this.currentReqRange.end.lineNumber
|
||||
) {
|
||||
// same request, now see if we are on the first line and update the action bar
|
||||
const cursorLineNumber = this.coreEditor.getCurrentPosition().lineNumber;
|
||||
if (cursorLineNumber === this.currentReqRange.start.lineNumber) {
|
||||
this.updateActionsBar();
|
||||
}
|
||||
return; // nothing to do..
|
||||
}
|
||||
|
||||
if (this.currentReqRange) {
|
||||
this.coreEditor.removeMarker(this.currentReqRange.markerRef);
|
||||
}
|
||||
|
||||
this.currentReqRange = expandedRange as any;
|
||||
if (this.currentReqRange) {
|
||||
this.currentReqRange.markerRef = this.coreEditor.addMarker(this.currentReqRange);
|
||||
}
|
||||
this.updateActionsBar();
|
||||
}, 25);
|
||||
|
||||
getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise<string> => {
|
||||
const variables = getStorage().get(StorageKeys.VARIABLES, DEFAULT_VARIABLES);
|
||||
let requests = await this.getRequestsInRange(range, true);
|
||||
requests = utils.replaceVariables(requests, variables);
|
||||
const result = _.map(requests, (req) => {
|
||||
if (typeof req === 'string') {
|
||||
// no request block
|
||||
return req;
|
||||
}
|
||||
|
||||
const path = req.url;
|
||||
const method = req.method;
|
||||
const data = req.data;
|
||||
|
||||
// this is the first url defined in elasticsearch.hosts
|
||||
const url = constructUrl(elasticsearchBaseUrl, path);
|
||||
|
||||
// Append 'kbn-xsrf' header to bypass (XSRF/CSRF) protections
|
||||
let ret = `curl -X${method.toUpperCase()} "${url}" -H "kbn-xsrf: reporting"`;
|
||||
|
||||
if (data && data.length) {
|
||||
const joinedData = data.join('\n');
|
||||
let dataAsString: string;
|
||||
|
||||
try {
|
||||
ret += ` -H "Content-Type: application/json" -d'\n`;
|
||||
|
||||
if (utils.hasComments(joinedData)) {
|
||||
// if there are comments in the data, we need to strip them out
|
||||
const dataWithoutComments = parse(joinedData);
|
||||
dataAsString = collapseLiteralStrings(JSON.stringify(dataWithoutComments, null, 2));
|
||||
} else {
|
||||
dataAsString = collapseLiteralStrings(joinedData);
|
||||
}
|
||||
// We escape single quoted strings that are wrapped in single quoted strings
|
||||
ret += dataAsString.replace(/'/g, "'\\''");
|
||||
if (data.length > 1) {
|
||||
ret += '\n';
|
||||
} // end with a new line
|
||||
ret += "'";
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing data: ${e.message}`);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
return result.join('\n');
|
||||
};
|
||||
|
||||
updateActionsBar = () => {
|
||||
return this.coreEditor.legacyUpdateUI(this.currentReqRange);
|
||||
};
|
||||
|
||||
getCoreEditor() {
|
||||
return this.coreEditor;
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import { produce } from 'immer';
|
|||
import { identity } from 'fp-ts/lib/function';
|
||||
import { DevToolsSettings, DEFAULT_SETTINGS } from '../../services';
|
||||
import { TextObject } from '../../../common/text_object';
|
||||
import { SenseEditor } from '../models';
|
||||
import { SHELL_TAB_ID } from '../containers/main/constants';
|
||||
import { MonacoEditorActionsProvider } from '../containers/editor/monaco_editor_actions_provider';
|
||||
import { RequestToRestore } from '../../types';
|
||||
|
@ -39,7 +38,7 @@ export const initialValue: Store = produce<Store>(
|
|||
);
|
||||
|
||||
export type Action =
|
||||
| { type: 'setInputEditor'; payload: SenseEditor | MonacoEditorActionsProvider }
|
||||
| { type: 'setInputEditor'; payload: MonacoEditorActionsProvider }
|
||||
| { type: 'setCurrentTextObject'; payload: TextObject }
|
||||
| { type: 'updateSettings'; payload: DevToolsSettings }
|
||||
| { type: 'setCurrentView'; payload: string }
|
||||
|
|
|
@ -1,10 +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".
|
||||
*/
|
||||
|
||||
export * from './token_provider';
|
|
@ -1,223 +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 '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
// TODO:
|
||||
// We import from application models as a convenient way to bootstrap loading up of an editor using
|
||||
// this lib. We also need to import application specific mocks which is not ideal.
|
||||
// In this situation, the token provider lib knows about app models in tests, which it really shouldn't. Should create
|
||||
// a better sandbox in future.
|
||||
import { create, SenseEditor } from '../../application/models/sense_editor';
|
||||
|
||||
import { Position, Token, TokensProvider } from '../../types';
|
||||
|
||||
interface RunTestArgs {
|
||||
input: string;
|
||||
done?: () => void;
|
||||
}
|
||||
|
||||
describe('Ace (legacy) token provider', () => {
|
||||
let senseEditor: SenseEditor;
|
||||
let tokenProvider: TokensProvider;
|
||||
beforeEach(() => {
|
||||
// Set up our document body
|
||||
document.body.innerHTML = `<div>
|
||||
<div id="ConAppEditor" />
|
||||
<div id="ConAppEditorActions" />
|
||||
<div id="ConCopyAsCurl" />
|
||||
</div>`;
|
||||
|
||||
senseEditor = create(document.querySelector<HTMLElement>('#ConAppEditor')!);
|
||||
|
||||
$(senseEditor.getCoreEditor().getContainer())!.show();
|
||||
|
||||
(senseEditor as any).autocomplete._test.removeChangeListener();
|
||||
tokenProvider = senseEditor.getCoreEditor().getTokenProvider();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
$(senseEditor.getCoreEditor().getContainer())!.hide();
|
||||
(senseEditor as any).autocomplete._test.addChangeListener();
|
||||
await senseEditor.update('', true);
|
||||
});
|
||||
|
||||
describe('#getTokens', () => {
|
||||
const runTest = ({
|
||||
input,
|
||||
expectedTokens,
|
||||
done,
|
||||
lineNumber = 1,
|
||||
}: RunTestArgs & { expectedTokens: Token[] | null; lineNumber?: number }) => {
|
||||
senseEditor.update(input, true).then(() => {
|
||||
const tokens = tokenProvider.getTokens(lineNumber);
|
||||
expect(tokens).toEqual(expectedTokens);
|
||||
if (done) done();
|
||||
});
|
||||
};
|
||||
|
||||
describe('base cases', () => {
|
||||
test('case 1 - only url', (done) => {
|
||||
runTest({
|
||||
input: `GET http://somehost/_search`,
|
||||
expectedTokens: [
|
||||
{ type: 'method', value: 'GET', position: { lineNumber: 1, column: 1 } },
|
||||
{ type: 'whitespace', value: ' ', position: { lineNumber: 1, column: 4 } },
|
||||
{
|
||||
type: 'url.protocol_host',
|
||||
value: 'http://somehost',
|
||||
position: { lineNumber: 1, column: 5 },
|
||||
},
|
||||
{ type: 'url.slash', value: '/', position: { lineNumber: 1, column: 20 } },
|
||||
{ type: 'url.part', value: '_search', position: { lineNumber: 1, column: 21 } },
|
||||
],
|
||||
done,
|
||||
});
|
||||
});
|
||||
|
||||
test('case 2 - basic auth in host name', (done) => {
|
||||
runTest({
|
||||
input: `GET http://test:user@somehost/`,
|
||||
expectedTokens: [
|
||||
{ type: 'method', value: 'GET', position: { lineNumber: 1, column: 1 } },
|
||||
{ type: 'whitespace', value: ' ', position: { lineNumber: 1, column: 4 } },
|
||||
{
|
||||
type: 'url.protocol_host',
|
||||
value: 'http://test:user@somehost',
|
||||
position: { lineNumber: 1, column: 5 },
|
||||
},
|
||||
{ type: 'url.slash', value: '/', position: { lineNumber: 1, column: 30 } },
|
||||
],
|
||||
done,
|
||||
});
|
||||
});
|
||||
|
||||
test('case 3 - handles empty lines', (done) => {
|
||||
runTest({
|
||||
input: `POST abc
|
||||
|
||||
|
||||
{
|
||||
`,
|
||||
expectedTokens: [
|
||||
{ type: 'method', value: 'POST', position: { lineNumber: 1, column: 1 } },
|
||||
{ type: 'whitespace', value: ' ', position: { lineNumber: 1, column: 5 } },
|
||||
{ type: 'url.part', value: 'abc', position: { lineNumber: 1, column: 6 } },
|
||||
],
|
||||
done,
|
||||
lineNumber: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with newlines', () => {
|
||||
test('case 1 - newlines base case', (done) => {
|
||||
runTest({
|
||||
input: `GET http://test:user@somehost/
|
||||
{
|
||||
"wudup": "!"
|
||||
}`,
|
||||
expectedTokens: [
|
||||
{ type: 'whitespace', value: ' ', position: { lineNumber: 3, column: 1 } },
|
||||
{ type: 'variable', value: '"wudup"', position: { lineNumber: 3, column: 3 } },
|
||||
{ type: 'punctuation.colon', value: ':', position: { lineNumber: 3, column: 10 } },
|
||||
{ type: 'whitespace', value: ' ', position: { lineNumber: 3, column: 11 } },
|
||||
{ type: 'string', value: '"!"', position: { lineNumber: 3, column: 12 } },
|
||||
],
|
||||
done,
|
||||
lineNumber: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
test('case 1 - getting token outside of document', (done) => {
|
||||
runTest({
|
||||
input: `GET http://test:user@somehost/
|
||||
{
|
||||
"wudup": "!"
|
||||
}`,
|
||||
expectedTokens: null,
|
||||
done,
|
||||
lineNumber: 100,
|
||||
});
|
||||
});
|
||||
|
||||
test('case 2 - empty lines', (done) => {
|
||||
runTest({
|
||||
input: `GET http://test:user@somehost/
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
"wudup": "!"
|
||||
}`,
|
||||
expectedTokens: [],
|
||||
done,
|
||||
lineNumber: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getTokenAt', () => {
|
||||
const runTest = ({
|
||||
input,
|
||||
expectedToken,
|
||||
done,
|
||||
position,
|
||||
}: RunTestArgs & { expectedToken: Token | null; position: Position }) => {
|
||||
senseEditor.update(input, true).then(() => {
|
||||
const tokens = tokenProvider.getTokenAt(position);
|
||||
expect(tokens).toEqual(expectedToken);
|
||||
if (done) done();
|
||||
});
|
||||
};
|
||||
|
||||
describe('base cases', () => {
|
||||
it('case 1 - gets a token from the url', (done) => {
|
||||
const input = `GET http://test:user@somehost/`;
|
||||
runTest({
|
||||
input,
|
||||
expectedToken: {
|
||||
position: { lineNumber: 1, column: 4 },
|
||||
type: 'whitespace',
|
||||
value: ' ',
|
||||
},
|
||||
position: { lineNumber: 1, column: 5 },
|
||||
});
|
||||
|
||||
runTest({
|
||||
input,
|
||||
expectedToken: {
|
||||
position: { lineNumber: 1, column: 5 },
|
||||
type: 'url.protocol_host',
|
||||
value: 'http://test:user@somehost',
|
||||
},
|
||||
position: { lineNumber: 1, column: input.length },
|
||||
done,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('special cases', () => {
|
||||
it('case 1 - handles input outside of range', (done) => {
|
||||
runTest({
|
||||
input: `GET abc`,
|
||||
expectedToken: null,
|
||||
done,
|
||||
position: { lineNumber: 1, column: 99 },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,84 +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 { IEditSession, TokenInfo as BraceTokenInfo } from 'brace';
|
||||
import { TokensProvider, Token, Position } from '../../types';
|
||||
|
||||
// Brace's token information types are not accurate.
|
||||
interface TokenInfo extends BraceTokenInfo {
|
||||
type: string;
|
||||
}
|
||||
|
||||
const toToken = (lineNumber: number, column: number, token: TokenInfo): Token => ({
|
||||
type: token.type,
|
||||
value: token.value,
|
||||
position: {
|
||||
lineNumber,
|
||||
column,
|
||||
},
|
||||
});
|
||||
|
||||
const toTokens = (lineNumber: number, tokens: TokenInfo[]): Token[] => {
|
||||
let acc = '';
|
||||
return tokens.map((token) => {
|
||||
const column = acc.length + 1;
|
||||
acc += token.value;
|
||||
return toToken(lineNumber, column, token);
|
||||
});
|
||||
};
|
||||
|
||||
const extractTokenFromAceTokenRow = (
|
||||
lineNumber: number,
|
||||
column: number,
|
||||
aceTokens: TokenInfo[]
|
||||
) => {
|
||||
let acc = '';
|
||||
for (const token of aceTokens) {
|
||||
const start = acc.length + 1;
|
||||
acc += token.value;
|
||||
const end = acc.length;
|
||||
if (column < start) continue;
|
||||
if (column > end + 1) continue;
|
||||
return toToken(lineNumber, start, token);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export class AceTokensProvider implements TokensProvider {
|
||||
constructor(private readonly session: IEditSession) {}
|
||||
|
||||
getTokens(lineNumber: number): Token[] | null {
|
||||
if (lineNumber < 1) return null;
|
||||
|
||||
// Important: must use a .session.getLength because this is a cached value.
|
||||
// Calculating line length here will lead to performance issues because this function
|
||||
// may be called inside of tight loops.
|
||||
const lineCount = this.session.getLength();
|
||||
if (lineNumber > lineCount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokens = this.session.getTokens(lineNumber - 1) as unknown as TokenInfo[];
|
||||
if (!tokens || !tokens.length) {
|
||||
// We are inside of the document but have no tokens for this line. Return an empty
|
||||
// array to represent this empty line.
|
||||
return [];
|
||||
}
|
||||
|
||||
return toTokens(lineNumber, tokens);
|
||||
}
|
||||
|
||||
getTokenAt(pos: Position): Token | null {
|
||||
const tokens = this.session.getTokens(pos.lineNumber - 1) as unknown as TokenInfo[];
|
||||
if (tokens) {
|
||||
return extractTokenFromAceTokenRow(pos.lineNumber, pos.column, tokens);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,33 +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 { CoreEditor, Position } from '../../types';
|
||||
import { getCurrentMethodAndTokenPaths } from './autocomplete';
|
||||
import type RowParser from '../row_parser';
|
||||
|
||||
import { getTopLevelUrlCompleteComponents } from '../kb/kb';
|
||||
import { populateContext } from './engine';
|
||||
|
||||
export function getEndpointFromPosition(editor: CoreEditor, pos: Position, parser: RowParser) {
|
||||
const lineValue = editor.getLineValue(pos.lineNumber);
|
||||
const context = {
|
||||
...getCurrentMethodAndTokenPaths(
|
||||
editor,
|
||||
{
|
||||
column: lineValue.length + 1 /* Go to the very end of the line */,
|
||||
lineNumber: pos.lineNumber,
|
||||
},
|
||||
parser,
|
||||
true
|
||||
),
|
||||
};
|
||||
const components = getTopLevelUrlCompleteComponents(context.method);
|
||||
populateContext(context.urlTokenPath, context, editor, true, components);
|
||||
return context.endpoint;
|
||||
}
|
|
@ -1,224 +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 '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
|
||||
import { looksLikeTypingIn } from './looks_like_typing_in';
|
||||
import { create } from '../../application/models';
|
||||
import type { SenseEditor } from '../../application/models';
|
||||
import type { CoreEditor, Position, Token, TokensProvider } from '../../types';
|
||||
|
||||
describe('looksLikeTypingIn', () => {
|
||||
let editor: SenseEditor;
|
||||
let coreEditor: CoreEditor;
|
||||
let tokenProvider: TokensProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `<div>
|
||||
<div id="ConAppEditor" />
|
||||
<div id="ConAppEditorActions" />
|
||||
<div id="ConCopyAsCurl" />
|
||||
</div>`;
|
||||
editor = create(document.getElementById('ConAppEditor')!);
|
||||
coreEditor = editor.getCoreEditor();
|
||||
tokenProvider = coreEditor.getTokenProvider();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await editor.update('', true);
|
||||
});
|
||||
|
||||
describe('general typing in', () => {
|
||||
interface RunTestArgs {
|
||||
preamble: string;
|
||||
autocomplete?: string;
|
||||
input: string;
|
||||
}
|
||||
|
||||
const runTest = async ({ preamble, autocomplete, input }: RunTestArgs) => {
|
||||
const pos: Position = { lineNumber: 1, column: 1 };
|
||||
|
||||
await editor.update(preamble, true);
|
||||
pos.column += preamble.length;
|
||||
const lastEvaluatedToken = tokenProvider.getTokenAt(pos);
|
||||
|
||||
if (autocomplete !== undefined) {
|
||||
await editor.update(coreEditor.getValue() + autocomplete, true);
|
||||
pos.column += autocomplete.length;
|
||||
}
|
||||
|
||||
await editor.update(coreEditor.getValue() + input, true);
|
||||
pos.column += input.length;
|
||||
const currentToken = tokenProvider.getTokenAt(pos);
|
||||
|
||||
expect(lastEvaluatedToken).not.toBeNull();
|
||||
expect(currentToken).not.toBeNull();
|
||||
expect(looksLikeTypingIn(lastEvaluatedToken!, currentToken!, coreEditor)).toBe(true);
|
||||
};
|
||||
|
||||
const cases: RunTestArgs[] = [
|
||||
{ preamble: 'G', input: 'E' },
|
||||
{ preamble: 'GET .kibana', input: '/' },
|
||||
{ preamble: 'GET .kibana', input: ',' },
|
||||
{ preamble: 'GET .kibana', input: '?' },
|
||||
{ preamble: 'GET .kibana/', input: '_' },
|
||||
{ preamble: 'GET .kibana/', input: '?' },
|
||||
{ preamble: 'GET .kibana,', input: '.' },
|
||||
{ preamble: 'GET .kibana,', input: '?' },
|
||||
{ preamble: 'GET .kibana?', input: 'k' },
|
||||
{ preamble: 'GET .kibana?k', input: '=' },
|
||||
{ preamble: 'GET .kibana?k=', input: 'v' },
|
||||
{ preamble: 'GET .kibana?k=v', input: '&' },
|
||||
{ preamble: 'GET .kibana?k', input: '&' },
|
||||
{ preamble: 'GET .kibana?k&', input: 'k' },
|
||||
{ preamble: 'GET ', autocomplete: '.kibana', input: '/' },
|
||||
{ preamble: 'GET ', autocomplete: '.kibana', input: ',' },
|
||||
{ preamble: 'GET ', autocomplete: '.kibana', input: '?' },
|
||||
{ preamble: 'GET .ki', autocomplete: 'bana', input: '/' },
|
||||
{ preamble: 'GET .ki', autocomplete: 'bana', input: ',' },
|
||||
{ preamble: 'GET .ki', autocomplete: 'bana', input: '?' },
|
||||
{ preamble: 'GET _nodes/', autocomplete: 'stats', input: '/' },
|
||||
{ preamble: 'GET _nodes/sta', autocomplete: 'ts', input: '/' },
|
||||
{ preamble: 'GET _nodes/', autocomplete: 'jvm', input: ',' },
|
||||
{ preamble: 'GET _nodes/j', autocomplete: 'vm', input: ',' },
|
||||
{ preamble: 'GET _nodes/jvm,', autocomplete: 'os', input: ',' },
|
||||
{ preamble: 'GET .kibana,', autocomplete: '.security', input: ',' },
|
||||
{ preamble: 'GET .kibana,.sec', autocomplete: 'urity', input: ',' },
|
||||
{ preamble: 'GET .kibana,', autocomplete: '.security', input: '/' },
|
||||
{ preamble: 'GET .kibana,.sec', autocomplete: 'urity', input: '/' },
|
||||
{ preamble: 'GET .kibana,', autocomplete: '.security', input: '?' },
|
||||
{ preamble: 'GET .kibana,.sec', autocomplete: 'urity', input: '?' },
|
||||
{ preamble: 'GET .kibana/', autocomplete: '_search', input: '?' },
|
||||
{ preamble: 'GET .kibana/_se', autocomplete: 'arch', input: '?' },
|
||||
{ preamble: 'GET .kibana/_search?', autocomplete: 'expand_wildcards', input: '=' },
|
||||
{ preamble: 'GET .kibana/_search?exp', autocomplete: 'and_wildcards', input: '=' },
|
||||
{ preamble: 'GET .kibana/_search?expand_wildcards=', autocomplete: 'all', input: '&' },
|
||||
{ preamble: 'GET .kibana/_search?expand_wildcards=a', autocomplete: 'll', input: '&' },
|
||||
{ preamble: 'GET _cat/indices?s=index&', autocomplete: 'expand_wildcards', input: '=' },
|
||||
{ preamble: 'GET _cat/indices?s=index&exp', autocomplete: 'and_wildcards', input: '=' },
|
||||
{ preamble: 'GET _cat/indices?v&', autocomplete: 'expand_wildcards', input: '=' },
|
||||
{ preamble: 'GET _cat/indices?v&exp', autocomplete: 'and_wildcards', input: '=' },
|
||||
// autocomplete skips one iteration of token evaluation if user types in every letter
|
||||
{ preamble: 'GET .kibana', autocomplete: '/', input: '_' }, // token '/' may not be evaluated
|
||||
{ preamble: 'GET .kibana', autocomplete: ',', input: '.' }, // token ',' may not be evaluated
|
||||
{ preamble: 'GET .kibana', autocomplete: '?', input: 'k' }, // token '?' may not be evaluated
|
||||
];
|
||||
for (const c of cases) {
|
||||
const name =
|
||||
c.autocomplete === undefined
|
||||
? `'${c.preamble}' -> '${c.input}'`
|
||||
: `'${c.preamble}' -> '${c.autocomplete}' (autocomplte) -> '${c.input}'`;
|
||||
test(name, async () => runTest(c));
|
||||
}
|
||||
});
|
||||
|
||||
describe('first typing in', () => {
|
||||
test(`'' -> 'G'`, () => {
|
||||
// this is based on an implementation within the evaluateCurrentTokenAfterAChange function
|
||||
const lastEvaluatedToken = { position: { column: 0, lineNumber: 0 }, value: '', type: '' };
|
||||
lastEvaluatedToken.position.lineNumber = coreEditor.getCurrentPosition().lineNumber;
|
||||
|
||||
const currentToken = { position: { column: 1, lineNumber: 1 }, value: 'G', type: 'method' };
|
||||
expect(looksLikeTypingIn(lastEvaluatedToken, currentToken, coreEditor)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
const matrices = [
|
||||
`
|
||||
GET .kibana/
|
||||
|
||||
|
||||
`
|
||||
.slice(1, -1)
|
||||
.split('\n'),
|
||||
`
|
||||
|
||||
POST test/_doc
|
||||
{"message": "test"}
|
||||
|
||||
GET /_cat/indices?v&s=
|
||||
|
||||
DE
|
||||
`
|
||||
.slice(1, -1)
|
||||
.split('\n'),
|
||||
`
|
||||
|
||||
PUT test/_doc/1
|
||||
{"field": "value"}
|
||||
`
|
||||
.slice(1, -1)
|
||||
.split('\n'),
|
||||
];
|
||||
|
||||
describe('navigating the editor via keyboard arrow keys', () => {
|
||||
const runHorizontalZigzagWalkTest = async (matrix: string[]) => {
|
||||
const width = matrix[0].length;
|
||||
const height = matrix.length;
|
||||
|
||||
await editor.update(matrix.join('\n'), true);
|
||||
let lastEvaluatedToken = tokenProvider.getTokenAt(coreEditor.getCurrentPosition());
|
||||
let currentToken: Token | null;
|
||||
|
||||
for (let i = 1; i < height * width * 2; i++) {
|
||||
const pos = {
|
||||
column: 1 + (i % width),
|
||||
lineNumber: 1 + Math.floor(i / width),
|
||||
};
|
||||
if (pos.lineNumber % 2 === 0) {
|
||||
pos.column = width - pos.column + 1;
|
||||
}
|
||||
if (pos.lineNumber > height) {
|
||||
pos.lineNumber = 2 * height - pos.lineNumber + 1;
|
||||
}
|
||||
|
||||
currentToken = tokenProvider.getTokenAt(pos);
|
||||
expect(lastEvaluatedToken).not.toBeNull();
|
||||
expect(currentToken).not.toBeNull();
|
||||
expect(looksLikeTypingIn(lastEvaluatedToken!, currentToken!, coreEditor)).toBe(false);
|
||||
lastEvaluatedToken = currentToken;
|
||||
}
|
||||
};
|
||||
|
||||
for (const matrix of matrices) {
|
||||
test(`horizontal zigzag walk ${matrix[0].length}x${matrix.length} map`, () =>
|
||||
runHorizontalZigzagWalkTest(matrix));
|
||||
}
|
||||
});
|
||||
|
||||
describe('clicking around the editor', () => {
|
||||
const runRandomClickingTest = async (matrix: string[], attempts: number) => {
|
||||
const width = matrix[0].length;
|
||||
const height = matrix.length;
|
||||
|
||||
await editor.update(matrix.join('\n'), true);
|
||||
let lastEvaluatedToken = tokenProvider.getTokenAt(coreEditor.getCurrentPosition());
|
||||
let currentToken: Token | null;
|
||||
|
||||
for (let i = 1; i < attempts; i++) {
|
||||
const pos = {
|
||||
column: Math.ceil(Math.random() * width),
|
||||
lineNumber: Math.ceil(Math.random() * height),
|
||||
};
|
||||
|
||||
currentToken = tokenProvider.getTokenAt(pos);
|
||||
expect(lastEvaluatedToken).not.toBeNull();
|
||||
expect(currentToken).not.toBeNull();
|
||||
expect(looksLikeTypingIn(lastEvaluatedToken!, currentToken!, coreEditor)).toBe(false);
|
||||
lastEvaluatedToken = currentToken;
|
||||
}
|
||||
};
|
||||
|
||||
for (const matrix of matrices) {
|
||||
const attempts = 4 * matrix[0].length * matrix.length;
|
||||
test(`random clicking ${matrix[0].length}x${matrix.length} map ${attempts} times`, () =>
|
||||
runRandomClickingTest(matrix, attempts));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,109 +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 type { CoreEditor, Position, Token } from '../../types';
|
||||
|
||||
enum Move {
|
||||
ForwardOneCharacter = 1,
|
||||
ForwardOneToken, // the column position may jump to the next token by autocomplete
|
||||
ForwardTwoTokens, // the column position could jump two tokens due to autocomplete
|
||||
}
|
||||
|
||||
const knownTypingInTokenTypes = new Map<Move, Map<string, Set<string>>>([
|
||||
[
|
||||
Move.ForwardOneCharacter,
|
||||
new Map<string, Set<string>>([
|
||||
// a pair of the last evaluated token type and a set of the current token types
|
||||
['', new Set(['method'])],
|
||||
['url.amp', new Set(['url.param'])],
|
||||
['url.comma', new Set(['url.part', 'url.questionmark'])],
|
||||
['url.equal', new Set(['url.value'])],
|
||||
['url.param', new Set(['url.amp', 'url.equal'])],
|
||||
['url.questionmark', new Set(['url.param'])],
|
||||
['url.slash', new Set(['url.part', 'url.questionmark'])],
|
||||
['url.value', new Set(['url.amp'])],
|
||||
]),
|
||||
],
|
||||
[
|
||||
Move.ForwardOneToken,
|
||||
new Map<string, Set<string>>([
|
||||
['method', new Set(['url.part'])],
|
||||
['url.amp', new Set(['url.amp', 'url.equal'])],
|
||||
['url.comma', new Set(['url.comma', 'url.questionmark', 'url.slash'])],
|
||||
['url.equal', new Set(['url.amp'])],
|
||||
['url.param', new Set(['url.equal'])],
|
||||
['url.part', new Set(['url.comma', 'url.questionmark', 'url.slash'])],
|
||||
['url.questionmark', new Set(['url.equal'])],
|
||||
['url.slash', new Set(['url.comma', 'url.questionmark', 'url.slash'])],
|
||||
['url.value', new Set(['url.amp'])],
|
||||
['whitespace', new Set(['url.comma', 'url.questionmark', 'url.slash'])],
|
||||
]),
|
||||
],
|
||||
[
|
||||
Move.ForwardTwoTokens,
|
||||
new Map<string, Set<string>>([['url.part', new Set(['url.param', 'url.part'])]]),
|
||||
],
|
||||
]);
|
||||
|
||||
const getOneCharacterNextOnTheRight = (pos: Position, coreEditor: CoreEditor): string => {
|
||||
const range = {
|
||||
start: { column: pos.column + 1, lineNumber: pos.lineNumber },
|
||||
end: { column: pos.column + 2, lineNumber: pos.lineNumber },
|
||||
};
|
||||
return coreEditor.getValueInRange(range);
|
||||
};
|
||||
|
||||
/**
|
||||
* Examines a change from the last evaluated to the current token and one
|
||||
* character next to the current token position on the right. Returns true if
|
||||
* the change looks like typing in, false otherwise.
|
||||
*
|
||||
* This function is supposed to filter out situations where autocomplete is not
|
||||
* preferable, such as clicking around the editor, navigating the editor via
|
||||
* keyboard arrow keys, etc.
|
||||
*/
|
||||
export const looksLikeTypingIn = (
|
||||
lastEvaluatedToken: Token,
|
||||
currentToken: Token,
|
||||
coreEditor: CoreEditor
|
||||
): boolean => {
|
||||
// if the column position moves to the right in the same line and the current
|
||||
// token length is 1, then user is possibly typing in a character.
|
||||
if (
|
||||
lastEvaluatedToken.position.column < currentToken.position.column &&
|
||||
lastEvaluatedToken.position.lineNumber === currentToken.position.lineNumber &&
|
||||
currentToken.value.length === 1 &&
|
||||
getOneCharacterNextOnTheRight(currentToken.position, coreEditor) === ''
|
||||
) {
|
||||
const moves =
|
||||
lastEvaluatedToken.position.column + 1 === currentToken.position.column
|
||||
? [Move.ForwardOneCharacter]
|
||||
: [Move.ForwardOneToken, Move.ForwardTwoTokens];
|
||||
for (const move of moves) {
|
||||
const tokenTypesPairs = knownTypingInTokenTypes.get(move) ?? new Map<string, Set<string>>();
|
||||
const currentTokenTypes = tokenTypesPairs.get(lastEvaluatedToken.type) ?? new Set<string>();
|
||||
if (currentTokenTypes.has(currentToken.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the column or the line number have changed for the last token or
|
||||
// user did not provided a new value, then we should not show autocomplete
|
||||
// this guards against triggering autocomplete when clicking around the editor
|
||||
if (
|
||||
lastEvaluatedToken.position.column !== currentToken.position.column ||
|
||||
lastEvaluatedToken.position.lineNumber !== currentToken.position.lineNumber ||
|
||||
lastEvaluatedToken.value === currentToken.value
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
|
@ -7,7 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
import { setAutocompleteInfo, AutocompleteInfo } from '../../services';
|
||||
import { expandAliases } from './expand_aliases';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
==========
|
||||
Curl 1
|
||||
-------------------------------------
|
||||
curl -XPUT 'http://localhost:9200/twitter/tweet/1' -d '{
|
||||
"user" : "kimchy",
|
||||
"post_date" : "2009-11-15T14:12:12",
|
||||
"message" : "trying out Elastic Search"
|
||||
}'
|
||||
-------------------------------------
|
||||
PUT /twitter/tweet/1
|
||||
{
|
||||
"user" : "kimchy",
|
||||
"post_date" : "2009-11-15T14:12:12",
|
||||
"message" : "trying out Elastic Search"
|
||||
}
|
||||
==========
|
||||
Curl 2
|
||||
-------------------------------------
|
||||
curl -XGET "localhost/twitter/tweet/1?version=2" -d '{
|
||||
"message" : "elasticsearch now has versioning support, double cool!"
|
||||
}'
|
||||
-------------------------------------
|
||||
GET /twitter/tweet/1?version=2
|
||||
{
|
||||
"message" : "elasticsearch now has versioning support, double cool!"
|
||||
}
|
||||
===========
|
||||
Curl 3
|
||||
-------------------------------------
|
||||
curl -XPOST https://localhost/twitter/tweet/1?version=2 -d '{
|
||||
"message" : "elasticsearch now has versioning support, double cool!"
|
||||
}'
|
||||
-------------------------------------
|
||||
POST /twitter/tweet/1?version=2
|
||||
{
|
||||
"message" : "elasticsearch now has versioning support, double cool!"
|
||||
}
|
||||
=========
|
||||
Curl 4
|
||||
-------------------------------------
|
||||
curl -XPOST https://localhost/twitter
|
||||
-------------------------------------
|
||||
POST /twitter
|
||||
==========
|
||||
Curl 5
|
||||
-------------------------------------
|
||||
curl -X POST https://localhost/twitter/
|
||||
-------------------------------------
|
||||
POST /twitter/
|
||||
=============
|
||||
Curl 6
|
||||
-------------------------------------
|
||||
curl -s -XPOST localhost:9200/missing-test -d'
|
||||
{
|
||||
"mappings": {
|
||||
}
|
||||
}'
|
||||
-------------------------------------
|
||||
POST /missing-test
|
||||
{
|
||||
"mappings": {
|
||||
}
|
||||
}
|
||||
=========================
|
||||
Curl 7
|
||||
-------------------------------------
|
||||
curl 'localhost:9200/missing-test/doc/_search?pretty' -d'
|
||||
{
|
||||
"query": {
|
||||
},
|
||||
}'
|
||||
-------------------------------------
|
||||
GET /missing-test/doc/_search?pretty
|
||||
{
|
||||
"query": {
|
||||
},
|
||||
}
|
||||
===========================
|
||||
Curl 8
|
||||
-------------------------------------
|
||||
curl localhost:9200/ -d'
|
||||
{
|
||||
"query": {
|
||||
}
|
||||
}'
|
||||
-------------------------------------
|
||||
GET /
|
||||
{
|
||||
"query": {
|
||||
}
|
||||
}
|
||||
====================================
|
||||
Curl Script
|
||||
-------------------------------------
|
||||
#!bin/sh
|
||||
|
||||
// test something
|
||||
curl 'localhost:9200/missing-test/doc/_search?pretty' -d'
|
||||
{
|
||||
"query": {
|
||||
},
|
||||
}'
|
||||
|
||||
|
||||
curl -XPOST https://localhost/twitter
|
||||
|
||||
#someother comments
|
||||
curl localhost:9200/ -d'
|
||||
{
|
||||
"query": {
|
||||
}
|
||||
}'
|
||||
|
||||
|
||||
-------------------
|
||||
# test something
|
||||
GET /missing-test/doc/_search?pretty
|
||||
{
|
||||
"query": {
|
||||
},
|
||||
}
|
||||
|
||||
POST /twitter
|
||||
|
||||
#someother comments
|
||||
GET /
|
||||
{
|
||||
"query": {
|
||||
}
|
||||
}
|
||||
====================================
|
||||
Curl with some text
|
||||
-------------------------------------
|
||||
This is what I meant:
|
||||
|
||||
curl 'localhost:9200/missing-test/doc/_search?'
|
||||
|
||||
This, however, does work:
|
||||
curl 'localhost:9200/missing/doc/_search?'
|
||||
-------------------
|
||||
### This is what I meant:
|
||||
|
||||
GET /missing-test/doc/_search?
|
||||
|
||||
### This, however, does work:
|
||||
GET /missing/doc/_search?
|
|
@ -1,194 +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".
|
||||
*/
|
||||
|
||||
function detectCURLinLine(line) {
|
||||
// returns true if text matches a curl request
|
||||
return line.match(/^\s*?curl\s+(-X[A-Z]+)?\s*['"]?.*?['"]?(\s*$|\s+?-d\s*?['"])/);
|
||||
}
|
||||
|
||||
export function detectCURL(text) {
|
||||
// returns true if text matches a curl request
|
||||
if (!text) return false;
|
||||
for (const line of text.split('\n')) {
|
||||
if (detectCURLinLine(line)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseCURL(text) {
|
||||
let state = 'NONE';
|
||||
const out = [];
|
||||
let body = [];
|
||||
let line = '';
|
||||
const lines = text.trim().split('\n');
|
||||
let matches;
|
||||
|
||||
const EmptyLine = /^\s*$/;
|
||||
const Comment = /^\s*(?:#|\/{2,})(.*)\n?$/;
|
||||
const ExecutionComment = /^\s*#!/;
|
||||
const ClosingSingleQuote = /^([^']*)'/;
|
||||
const ClosingDoubleQuote = /^((?:[^\\"]|\\.)*)"/;
|
||||
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;
|
||||
|
||||
const LooksLikeCurl = /^\s*curl\s+/;
|
||||
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;
|
||||
|
||||
const HasProtocol = /[\s"']https?:\/\//;
|
||||
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
|
||||
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
|
||||
const CurlData = /^.+\s(--data|-d)\s*/;
|
||||
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;
|
||||
|
||||
if (lines.length > 0 && ExecutionComment.test(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
function nextLine() {
|
||||
if (line.length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (lines.length === 0) {
|
||||
return false;
|
||||
}
|
||||
line = lines.shift().replace(/[\r\n]+/g, '\n') + '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
function unescapeLastBodyEl() {
|
||||
const str = body.pop().replace(/\\([\\"'])/g, '$1');
|
||||
body.push(str);
|
||||
}
|
||||
|
||||
// Is the next char a single or double quote?
|
||||
// If so remove it
|
||||
function detectQuote() {
|
||||
if (line.substr(0, 1) === "'") {
|
||||
line = line.substr(1);
|
||||
state = 'SINGLE_QUOTE';
|
||||
} else if (line.substr(0, 1) === '"') {
|
||||
line = line.substr(1);
|
||||
state = 'DOUBLE_QUOTE';
|
||||
} else {
|
||||
state = 'UNQUOTED';
|
||||
}
|
||||
}
|
||||
|
||||
// Body is finished - append to output with final LF
|
||||
function addBodyToOut() {
|
||||
if (body.length > 0) {
|
||||
out.push(body.join(''));
|
||||
body = [];
|
||||
}
|
||||
state = 'LF';
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
// If the pattern matches, then the state is about to change,
|
||||
// so add the capture to the body and detect the next state
|
||||
// Otherwise add the whole line
|
||||
function consumeMatching(pattern) {
|
||||
const matches = line.match(pattern);
|
||||
if (matches) {
|
||||
body.push(matches[1]);
|
||||
line = line.substr(matches[0].length);
|
||||
detectQuote();
|
||||
} else {
|
||||
body.push(line);
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
function parseCurlLine() {
|
||||
let verb = 'GET';
|
||||
let request = '';
|
||||
let matches;
|
||||
if ((matches = line.match(CurlVerb))) {
|
||||
verb = matches[1];
|
||||
}
|
||||
|
||||
// JS regexen don't support possessive quantifiers, so
|
||||
// we need two distinct patterns
|
||||
const pattern = HasProtocol.test(line) ? CurlRequestWithProto : CurlRequestWithoutProto;
|
||||
|
||||
if ((matches = line.match(pattern))) {
|
||||
request = matches[1];
|
||||
}
|
||||
|
||||
out.push(verb + ' /' + request + '\n');
|
||||
|
||||
if ((matches = line.match(CurlData))) {
|
||||
line = line.substr(matches[0].length);
|
||||
detectQuote();
|
||||
if (EmptyLine.test(line)) {
|
||||
line = '';
|
||||
}
|
||||
} else {
|
||||
state = 'NONE';
|
||||
line = '';
|
||||
out.push('');
|
||||
}
|
||||
}
|
||||
|
||||
while (nextLine()) {
|
||||
if (state === 'SINGLE_QUOTE') {
|
||||
consumeMatching(ClosingSingleQuote);
|
||||
} else if (state === 'DOUBLE_QUOTE') {
|
||||
consumeMatching(ClosingDoubleQuote);
|
||||
unescapeLastBodyEl();
|
||||
} else if (state === 'UNQUOTED') {
|
||||
consumeMatching(EscapedQuotes);
|
||||
if (body.length) {
|
||||
unescapeLastBodyEl();
|
||||
}
|
||||
if (state === 'UNQUOTED') {
|
||||
addBodyToOut();
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
// the BODY state (used to match the body of a Sense request)
|
||||
// can be terminated early if it encounters
|
||||
// a comment or an empty line
|
||||
else if (state === 'BODY') {
|
||||
if (Comment.test(line) || EmptyLine.test(line)) {
|
||||
addBodyToOut();
|
||||
} else {
|
||||
body.push(line);
|
||||
line = '';
|
||||
}
|
||||
} else if (EmptyLine.test(line)) {
|
||||
if (state !== 'LF') {
|
||||
out.push('\n');
|
||||
state = 'LF';
|
||||
}
|
||||
line = '';
|
||||
} else if ((matches = line.match(Comment))) {
|
||||
out.push('#' + matches[1] + '\n');
|
||||
state = 'NONE';
|
||||
line = '';
|
||||
} else if (LooksLikeCurl.test(line)) {
|
||||
parseCurlLine();
|
||||
} else if ((matches = line.match(SenseLine))) {
|
||||
out.push(matches[1] + ' /' + matches[2] + '\n');
|
||||
line = '';
|
||||
state = 'BODY';
|
||||
}
|
||||
|
||||
// Nothing else matches, so output with a prefix of !!! for debugging purposes
|
||||
else {
|
||||
out.push('### ' + line);
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
addBodyToOut();
|
||||
return out.join('').trim();
|
||||
}
|
|
@ -1,37 +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 _ from 'lodash';
|
||||
import { detectCURL, parseCURL } from './curl';
|
||||
import curlTests from './__fixtures__/curl_parsing.txt';
|
||||
|
||||
describe('CURL', () => {
|
||||
const notCURLS = ['sldhfsljfhs', 's;kdjfsldkfj curl -XDELETE ""', '{ "hello": 1 }'];
|
||||
_.each(notCURLS, function (notCURL, i) {
|
||||
test('cURL Detection - broken strings ' + i, function () {
|
||||
expect(detectCURL(notCURL)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
curlTests.split(/^=+$/m).forEach(function (fixture) {
|
||||
if (fixture.trim() === '') {
|
||||
return;
|
||||
}
|
||||
fixture = fixture.split(/^-+$/m);
|
||||
const name = fixture[0].trim();
|
||||
const curlText = fixture[1];
|
||||
const response = fixture[2].trim();
|
||||
|
||||
test('cURL Detection - ' + name, function () {
|
||||
expect(detectCURL(curlText)).toBe(true);
|
||||
const r = parseCURL(curlText);
|
||||
expect(r).toEqual(response);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,7 +10,6 @@
|
|||
import _ from 'lodash';
|
||||
import { populateContext } from '../autocomplete/engine';
|
||||
|
||||
import '../../application/models/sense_editor/sense_editor.test.mocks';
|
||||
import * as kb from '.';
|
||||
import { AutocompleteInfo, setAutocompleteInfo } from '../../services';
|
||||
|
||||
|
|
|
@ -1,107 +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 '../application/models/legacy_core_editor/legacy_core_editor.test.mocks';
|
||||
|
||||
import RowParser from './row_parser';
|
||||
import { create, MODE } from '../application/models';
|
||||
import type { SenseEditor } from '../application/models';
|
||||
import type { CoreEditor } from '../types';
|
||||
|
||||
describe('RowParser', () => {
|
||||
let editor: SenseEditor | null;
|
||||
let parser: RowParser | null;
|
||||
|
||||
beforeEach(function () {
|
||||
// Set up our document body
|
||||
document.body.innerHTML = `<div>
|
||||
<div id="ConAppEditor" />
|
||||
<div id="ConAppEditorActions" />
|
||||
<div id="ConCopyAsCurl" />
|
||||
</div>`;
|
||||
editor = create(document.getElementById('ConAppEditor')!);
|
||||
parser = new RowParser(editor.getCoreEditor() as CoreEditor);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
editor?.getCoreEditor().destroy();
|
||||
editor = null;
|
||||
parser = null;
|
||||
});
|
||||
|
||||
describe('getRowParseMode', () => {
|
||||
const forceRetokenize = false;
|
||||
|
||||
it('should return MODE.BETWEEN_REQUESTS if line is empty', () => {
|
||||
editor?.getCoreEditor().setValue('', forceRetokenize);
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.BETWEEN_REQUESTS);
|
||||
});
|
||||
|
||||
it('should return MODE.BETWEEN_REQUESTS if line is a comment', () => {
|
||||
editor?.getCoreEditor().setValue('// comment', forceRetokenize);
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.BETWEEN_REQUESTS);
|
||||
});
|
||||
|
||||
it('should return MODE.REQUEST_START | MODE.REQUEST_END if line is a single line request', () => {
|
||||
editor?.getCoreEditor().setValue('GET _search', forceRetokenize);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.REQUEST_START | MODE.REQUEST_END);
|
||||
});
|
||||
|
||||
it('should return MODE.IN_REQUEST if line is a request with an opening curly brace', () => {
|
||||
editor?.getCoreEditor().setValue('{', forceRetokenize);
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.IN_REQUEST);
|
||||
});
|
||||
|
||||
it('should return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST if line is a multi doc request with an opening curly brace', () => {
|
||||
editor?.getCoreEditor().setValue('GET _msearch\n{}\n{', forceRetokenize);
|
||||
const lineNumber = editor?.getCoreEditor().getLineCount()! - 1;
|
||||
expect(parser?.getRowParseMode(lineNumber)).toBe(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST
|
||||
);
|
||||
});
|
||||
|
||||
it('should return MODE.MULTI_DOC_CUR_DOC_END | MODE.REQUEST_END if line is a multi doc request with a closing curly brace', () => {
|
||||
editor?.getCoreEditor().setValue('GET _msearch\n{}\n{"foo": 1}\n', forceRetokenize);
|
||||
const lineNumber = editor?.getCoreEditor().getLineCount()! - 1;
|
||||
expect(parser?.getRowParseMode(lineNumber)).toBe(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
MODE.MULTI_DOC_CUR_DOC_END | MODE.REQUEST_END
|
||||
);
|
||||
});
|
||||
|
||||
it('should return MODE.REQUEST_START | MODE.REQUEST_END if line is a request with variables', () => {
|
||||
editor?.getCoreEditor().setValue('GET /${exampleVariable}', forceRetokenize);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.REQUEST_START | MODE.REQUEST_END);
|
||||
});
|
||||
|
||||
it('should return MODE.REQUEST_START | MODE.REQUEST_END if a single request line ends with a closing curly brace', () => {
|
||||
editor?.getCoreEditor().setValue('DELETE <foo>/_bar/_baz%{test}', forceRetokenize);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
expect(parser?.getRowParseMode()).toBe(MODE.REQUEST_START | MODE.REQUEST_END);
|
||||
});
|
||||
|
||||
it('should return correct modes for multiple bulk requests', () => {
|
||||
editor
|
||||
?.getCoreEditor()
|
||||
.setValue('POST _bulk\n{"index": {"_index": "test"}}\n{"foo": "bar"}\n', forceRetokenize);
|
||||
expect(parser?.getRowParseMode(0)).toBe(MODE.BETWEEN_REQUESTS);
|
||||
editor
|
||||
?.getCoreEditor()
|
||||
.setValue('POST _bulk\n{"index": {"_index": "test"}}\n{"foo": "bar"}\n', forceRetokenize);
|
||||
const lineNumber = editor?.getCoreEditor().getLineCount()! - 1;
|
||||
expect(parser?.getRowParseMode(lineNumber)).toBe(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,161 +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 { CoreEditor, Token } from '../types';
|
||||
import { TokenIterator } from './token_iterator';
|
||||
|
||||
export const MODE = {
|
||||
REQUEST_START: 2,
|
||||
IN_REQUEST: 4,
|
||||
MULTI_DOC_CUR_DOC_END: 8,
|
||||
REQUEST_END: 16,
|
||||
BETWEEN_REQUESTS: 32,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class RowParser {
|
||||
constructor(private readonly editor: CoreEditor) {}
|
||||
|
||||
MODE = MODE;
|
||||
|
||||
getRowParseMode(lineNumber = this.editor.getCurrentPosition().lineNumber) {
|
||||
const linesCount = this.editor.getLineCount();
|
||||
if (lineNumber > linesCount || lineNumber < 1) {
|
||||
return MODE.BETWEEN_REQUESTS;
|
||||
}
|
||||
const mode = this.editor.getLineState(lineNumber);
|
||||
|
||||
if (!mode) {
|
||||
return MODE.BETWEEN_REQUESTS;
|
||||
} // shouldn't really happen
|
||||
// If another "start" mode is added here because we want to allow for new language highlighting
|
||||
// please see https://github.com/elastic/kibana/pull/51446 for a discussion on why
|
||||
// should consider a different approach.
|
||||
if (mode !== 'start' && mode !== 'start-sql') {
|
||||
return MODE.IN_REQUEST;
|
||||
}
|
||||
let line = (this.editor.getLineValue(lineNumber) || '').trim();
|
||||
|
||||
if (!line || line.startsWith('#') || line.startsWith('//') || line.startsWith('/*')) {
|
||||
return MODE.BETWEEN_REQUESTS;
|
||||
} // empty line or a comment waiting for a new req to start
|
||||
|
||||
// Check for multi doc requests
|
||||
if (line.endsWith('}') && !this.isRequestLine(line)) {
|
||||
// check for a multi doc request must start a new json doc immediately after this one end.
|
||||
lineNumber++;
|
||||
if (lineNumber < linesCount + 1) {
|
||||
line = (this.editor.getLineValue(lineNumber) || '').trim();
|
||||
if (line.indexOf('{') === 0) {
|
||||
// next line is another doc in a multi doc
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST;
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request
|
||||
}
|
||||
|
||||
// check for single line requests
|
||||
lineNumber++;
|
||||
if (lineNumber >= linesCount + 1) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return MODE.REQUEST_START | MODE.REQUEST_END;
|
||||
}
|
||||
line = (this.editor.getLineValue(lineNumber) || '').trim();
|
||||
if (line.indexOf('{') !== 0) {
|
||||
// next line is another request
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return MODE.REQUEST_START | MODE.REQUEST_END;
|
||||
}
|
||||
|
||||
return MODE.REQUEST_START;
|
||||
}
|
||||
|
||||
rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: number) {
|
||||
const mode = this.getRowParseMode(lineNumber);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return (mode & value) > 0;
|
||||
}
|
||||
|
||||
isEndRequestRow(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
return this.rowPredicate(row, editor, MODE.REQUEST_END);
|
||||
}
|
||||
|
||||
isRequestEdge(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START);
|
||||
}
|
||||
|
||||
isStartRequestRow(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
return this.rowPredicate(row, editor, MODE.REQUEST_START);
|
||||
}
|
||||
|
||||
isInBetweenRequestsRow(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS);
|
||||
}
|
||||
|
||||
isInRequestsRow(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
return this.rowPredicate(row, editor, MODE.IN_REQUEST);
|
||||
}
|
||||
|
||||
isMultiDocDocEndRow(row?: number, _e?: CoreEditor) {
|
||||
const editor = _e || this.editor;
|
||||
return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END);
|
||||
}
|
||||
|
||||
isEmptyToken(tokenOrTokenIter: TokenIterator | Token | null) {
|
||||
const token =
|
||||
tokenOrTokenIter && (tokenOrTokenIter as TokenIterator).getCurrentToken
|
||||
? (tokenOrTokenIter as TokenIterator).getCurrentToken()
|
||||
: tokenOrTokenIter;
|
||||
return !token || (token as Token).type === 'whitespace';
|
||||
}
|
||||
|
||||
isUrlOrMethodToken(tokenOrTokenIter: TokenIterator | Token) {
|
||||
const t = (tokenOrTokenIter as TokenIterator)?.getCurrentToken() ?? (tokenOrTokenIter as Token);
|
||||
return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0);
|
||||
}
|
||||
|
||||
nextNonEmptyToken(tokenIter: TokenIterator) {
|
||||
let t = tokenIter.stepForward();
|
||||
while (t && this.isEmptyToken(t)) {
|
||||
t = tokenIter.stepForward();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
prevNonEmptyToken(tokenIter: TokenIterator) {
|
||||
let t = tokenIter.stepBackward();
|
||||
// empty rows return null token.
|
||||
while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t))
|
||||
t = tokenIter.stepBackward();
|
||||
return t;
|
||||
}
|
||||
|
||||
isCommentToken(token: Token | null) {
|
||||
return (
|
||||
token &&
|
||||
token.type &&
|
||||
(token.type === 'comment.punctuation' ||
|
||||
token.type === 'comment.line' ||
|
||||
token.type === 'comment.block')
|
||||
);
|
||||
}
|
||||
|
||||
isRequestLine(line: string) {
|
||||
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS'];
|
||||
return methods.some((m) => line.startsWith(m));
|
||||
}
|
||||
}
|
|
@ -36,8 +36,6 @@
|
|||
width: 100%;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
|
||||
// Required on IE11 to render ace editor correctly after first input.
|
||||
position: relative;
|
||||
|
||||
&__spinner {
|
||||
|
@ -55,46 +53,6 @@
|
|||
height: 100%;
|
||||
display: flex;
|
||||
flex: 1 1 1px;
|
||||
|
||||
.ace_badge {
|
||||
font-family: $euiFontFamily;
|
||||
font-size: $euiFontSizeXS;
|
||||
font-weight: $euiFontWeightMedium;
|
||||
line-height: $euiLineHeight;
|
||||
padding: 0 $euiSizeS;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
border-radius: calc($euiBorderRadius / 2);
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
max-width: 100%;
|
||||
|
||||
&--success {
|
||||
background-color: $euiColorVis0_behindText;
|
||||
color: chooseLightOrDarkText($euiColorVis0_behindText);
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $euiColorVis5_behindText;
|
||||
color: chooseLightOrDarkText($euiColorVis5_behindText);
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $euiColorVis1_behindText;
|
||||
color: chooseLightOrDarkText($euiColorVis1_behindText);
|
||||
}
|
||||
|
||||
&--default {
|
||||
background-color: $euiColorLightShade;
|
||||
color: chooseLightOrDarkText($euiColorLightShade);
|
||||
}
|
||||
|
||||
&--danger {
|
||||
background-color: $euiColorVis9_behindText;
|
||||
color: chooseLightOrDarkText($euiColorVis9_behindText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.conApp__editorContent,
|
||||
|
@ -145,17 +103,6 @@
|
|||
margin-inline: 0;
|
||||
}
|
||||
|
||||
// SASSTODO: This component seems to not be used anymore?
|
||||
// Possibly replaced by the Ace version
|
||||
.conApp__autoComplete {
|
||||
position: absolute;
|
||||
left: -1000px;
|
||||
visibility: hidden;
|
||||
/* by pass any other element in ace and resize bar, but not modal popups */
|
||||
z-index: $euiZLevel1 + 2;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.conApp__requestProgressBarContainer {
|
||||
position: relative;
|
||||
z-index: $euiZLevel2;
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { Editor } from 'brace';
|
||||
import { ResultTerm } from '../lib/autocomplete/types';
|
||||
import { TokensProvider } from './tokens_provider';
|
||||
import { Token } from './token';
|
||||
|
@ -94,7 +93,7 @@ export enum LINE_MODE {
|
|||
/**
|
||||
* The CoreEditor is a component separate from the Editor implementation that provides Console
|
||||
* app specific business logic. The CoreEditor is an interface to the lower-level editor implementation
|
||||
* being used which is usually vendor code such as Ace or Monaco.
|
||||
* being used which is usually vendor code such as Monaco.
|
||||
*/
|
||||
export interface CoreEditor {
|
||||
/**
|
||||
|
@ -260,7 +259,7 @@ export interface CoreEditor {
|
|||
*/
|
||||
registerKeyboardShortcut(opts: {
|
||||
keys: string | { win?: string; mac?: string };
|
||||
fn: (editor: Editor) => void;
|
||||
fn: (editor: any) => void;
|
||||
name: string;
|
||||
}): void;
|
||||
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
"@kbn/i18n-react",
|
||||
"@kbn/shared-ux-utility",
|
||||
"@kbn/core-http-browser",
|
||||
"@kbn/ace",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/core-http-router-server-internal",
|
||||
"@kbn/web-worker-stub",
|
||||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/react-kibana-context-theme",
|
||||
|
|
|
@ -320,8 +320,6 @@
|
|||
"coloring.dynamicColoring.rangeType.label": "Type de valeur",
|
||||
"coloring.dynamicColoring.rangeType.number": "Numéro",
|
||||
"coloring.dynamicColoring.rangeType.percent": "Pourcent",
|
||||
"console.autocomplete.addMethodMetaText": "méthode",
|
||||
"console.autocomplete.fieldsFetchingAnnotation": "La récupération des champs est en cours",
|
||||
"console.autocompleteSuggestions.apiLabel": "API",
|
||||
"console.autocompleteSuggestions.endpointLabel": "point de terminaison",
|
||||
"console.autocompleteSuggestions.methodLabel": "méthode",
|
||||
|
@ -362,10 +360,6 @@
|
|||
"console.loadingError.title": "Impossible de charger la console",
|
||||
"console.notification.clearHistory": "Effacer l'historique",
|
||||
"console.notification.disableSavingToHistory": "Désactiver l'enregistrement",
|
||||
"console.notification.error.couldNotSaveRequestTitle": "Impossible d'enregistrer la requête dans l'historique de la console.",
|
||||
"console.notification.error.historyQuotaReachedMessage": "L'historique des requêtes est arrivé à saturation. Effacez l'historique de la console ou désactivez l'enregistrement de nouvelles requêtes.",
|
||||
"console.notification.error.noRequestSelectedTitle": "Aucune requête sélectionnée. Sélectionnez une requête en positionnant le curseur dessus.",
|
||||
"console.notification.error.unknownErrorTitle": "Erreur de requête inconnue",
|
||||
"console.pageHeading": "Console",
|
||||
"console.requestInProgressBadgeText": "Requête en cours",
|
||||
"console.requestOptions.autoIndentButtonLabel": "Appliquer les indentations",
|
||||
|
|
|
@ -320,8 +320,6 @@
|
|||
"coloring.dynamicColoring.rangeType.label": "値型",
|
||||
"coloring.dynamicColoring.rangeType.number": "Number",
|
||||
"coloring.dynamicColoring.rangeType.percent": "割合(%)",
|
||||
"console.autocomplete.addMethodMetaText": "メソド",
|
||||
"console.autocomplete.fieldsFetchingAnnotation": "フィールドの取得を実行しています",
|
||||
"console.autocompleteSuggestions.apiLabel": "API",
|
||||
"console.autocompleteSuggestions.endpointLabel": "エンドポイント",
|
||||
"console.autocompleteSuggestions.methodLabel": "メソド",
|
||||
|
@ -362,10 +360,6 @@
|
|||
"console.loadingError.title": "コンソールを読み込めません",
|
||||
"console.notification.clearHistory": "履歴を消去",
|
||||
"console.notification.disableSavingToHistory": "保存を無効にする",
|
||||
"console.notification.error.couldNotSaveRequestTitle": "リクエストをコンソール履歴に保存できませんでした。",
|
||||
"console.notification.error.historyQuotaReachedMessage": "リクエスト履歴が満杯です。コンソール履歴を消去するか、新しいリクエストの保存を無効にしてください。",
|
||||
"console.notification.error.noRequestSelectedTitle": "リクエストを選択していません。リクエストの中にカーソルを置いて選択します。",
|
||||
"console.notification.error.unknownErrorTitle": "不明なリクエストエラー",
|
||||
"console.pageHeading": "コンソール",
|
||||
"console.requestInProgressBadgeText": "リクエストが進行中",
|
||||
"console.requestOptions.autoIndentButtonLabel": "インデントを適用",
|
||||
|
|
|
@ -319,8 +319,6 @@
|
|||
"coloring.dynamicColoring.rangeType.label": "值类型",
|
||||
"coloring.dynamicColoring.rangeType.number": "数字",
|
||||
"coloring.dynamicColoring.rangeType.percent": "百分比",
|
||||
"console.autocomplete.addMethodMetaText": "方法",
|
||||
"console.autocomplete.fieldsFetchingAnnotation": "正在提取字段",
|
||||
"console.autocompleteSuggestions.apiLabel": "API",
|
||||
"console.autocompleteSuggestions.endpointLabel": "终端",
|
||||
"console.autocompleteSuggestions.methodLabel": "方法",
|
||||
|
@ -361,10 +359,6 @@
|
|||
"console.loadingError.title": "无法加载控制台",
|
||||
"console.notification.clearHistory": "清除历史记录",
|
||||
"console.notification.disableSavingToHistory": "禁止保存",
|
||||
"console.notification.error.couldNotSaveRequestTitle": "无法将请求保存到控制台历史记录。",
|
||||
"console.notification.error.historyQuotaReachedMessage": "请求历史记录已满。请清除控制台历史记录或禁止保存新的请求。",
|
||||
"console.notification.error.noRequestSelectedTitle": "未选择任何请求。将鼠标置于请求内即可选择。",
|
||||
"console.notification.error.unknownErrorTitle": "未知请求错误",
|
||||
"console.pageHeading": "控制台",
|
||||
"console.requestInProgressBadgeText": "进行中的请求",
|
||||
"console.requestOptions.autoIndentButtonLabel": "应用行首缩进",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue