mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Console] Fix the end range of selected requests (#189747)
## Summary Fixes https://github.com/elastic/kibana/issues/189366 Fixes https://github.com/elastic/kibana/issues/186773 This PR refactors how the request body is being extracted from the editor to use for "sendRequest" and "copyAsCurl" functions. Previously the editor actions provider would rely on the parser to get a JSON object or several for request body. The downside of this implementation was when the parser would not be able to fully process the json object. That could lead to potential text loss and the editor would process the requests in a way that was not always obvious to the user. For example, the editor would highlight the request with the json object, but when sending it to ES the request body would be completely ignored. Instead this PR suggests to use the "raw" text from the editor for actions and give the user more transparency and control over the requests. We also don't need to keep the information about requests in the parser, which might affect browser performance for longer texts. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
1053ec6c7b
commit
f3a6527437
14 changed files with 474 additions and 209 deletions
|
@ -37,24 +37,7 @@ export const createParser = () => {
|
|||
requestStartOffset = at - 1;
|
||||
requests.push({ startOffset: requestStartOffset });
|
||||
},
|
||||
addRequestMethod = function(method) {
|
||||
const lastRequest = getLastRequest();
|
||||
lastRequest.method = method;
|
||||
requests.push(lastRequest);
|
||||
requestEndOffset = at - 1;
|
||||
},
|
||||
addRequestUrl = function(url) {
|
||||
const lastRequest = getLastRequest();
|
||||
lastRequest.url = url;
|
||||
requests.push(lastRequest);
|
||||
requestEndOffset = at - 1;
|
||||
},
|
||||
addRequestData = function(data) {
|
||||
const lastRequest = getLastRequest();
|
||||
const dataArray = lastRequest.data || [];
|
||||
dataArray.push(data);
|
||||
lastRequest.data = dataArray;
|
||||
requests.push(lastRequest);
|
||||
updateRequestEnd = function () {
|
||||
requestEndOffset = at - 1;
|
||||
},
|
||||
addRequestEnd = function() {
|
||||
|
@ -409,17 +392,17 @@ export const createParser = () => {
|
|||
request = function () {
|
||||
white();
|
||||
addRequestStart();
|
||||
const parsedMethod = method();
|
||||
addRequestMethod(parsedMethod);
|
||||
method();
|
||||
updateRequestEnd();
|
||||
strictWhite();
|
||||
const parsedUrl = url();
|
||||
addRequestUrl(parsedUrl);
|
||||
url();
|
||||
updateRequestEnd();
|
||||
strictWhite(); // advance to one new line
|
||||
newLine();
|
||||
strictWhite();
|
||||
if (ch == '{') {
|
||||
const parsedObject = object();
|
||||
addRequestData(parsedObject);
|
||||
object();
|
||||
updateRequestEnd();
|
||||
}
|
||||
// multi doc request
|
||||
strictWhite(); // advance to one new line
|
||||
|
@ -427,8 +410,8 @@ export const createParser = () => {
|
|||
strictWhite();
|
||||
while (ch == '{') {
|
||||
// another object
|
||||
const parsedObject = object();
|
||||
addRequestData(parsedObject);
|
||||
object();
|
||||
updateRequestEnd();
|
||||
strictWhite();
|
||||
newLine();
|
||||
strictWhite();
|
||||
|
|
|
@ -25,9 +25,7 @@ describe('console parser', () => {
|
|||
const { requests, errors } = parser(input) as ConsoleParserResult;
|
||||
expect(requests.length).toBe(1);
|
||||
expect(errors.length).toBe(0);
|
||||
const { method, url, startOffset, endOffset } = requests[0];
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_search');
|
||||
const { startOffset, endOffset } = requests[0];
|
||||
// the start offset of the request is the beginning of the string
|
||||
expect(startOffset).toBe(0);
|
||||
// the end offset of the request is the end of the string
|
||||
|
@ -38,6 +36,10 @@ describe('console parser', () => {
|
|||
const input = 'GET _search\nPOST _test_index';
|
||||
const { requests } = parser(input) as ConsoleParserResult;
|
||||
expect(requests.length).toBe(2);
|
||||
expect(requests[0].startOffset).toBe(0);
|
||||
expect(requests[0].endOffset).toBe(11);
|
||||
expect(requests[1].startOffset).toBe(12);
|
||||
expect(requests[1].endOffset).toBe(28);
|
||||
});
|
||||
|
||||
it('parses a request with a request body', () => {
|
||||
|
@ -45,15 +47,8 @@ describe('console parser', () => {
|
|||
'GET _search\n' + '{\n' + ' "query": {\n' + ' "match_all": {}\n' + ' }\n' + '}';
|
||||
const { requests } = parser(input) as ConsoleParserResult;
|
||||
expect(requests.length).toBe(1);
|
||||
const { method, url, data } = requests[0];
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_search');
|
||||
expect(data).toEqual([
|
||||
{
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
]);
|
||||
const { startOffset, endOffset } = requests[0];
|
||||
expect(startOffset).toBe(0);
|
||||
expect(endOffset).toBe(52);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,9 +14,6 @@ export interface ErrorAnnotation {
|
|||
export interface ParsedRequest {
|
||||
startOffset: number;
|
||||
endOffset?: number;
|
||||
method: string;
|
||||
url?: string;
|
||||
data?: Array<Record<string, unknown>>;
|
||||
}
|
||||
export interface ConsoleParserResult {
|
||||
errors: ErrorAnnotation[];
|
||||
|
|
|
@ -11,6 +11,7 @@ import { debounce, range } from 'lodash';
|
|||
import { ConsoleParsedRequestsProvider, getParsedRequestsProvider, monaco } from '@kbn/monaco';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { XJson } from '@kbn/es-ui-shared-plugin/public';
|
||||
import { isQuotaExceededError } from '../../../../services/history';
|
||||
import { DEFAULT_VARIABLES } from '../../../../../common/constants';
|
||||
import { getStorage, StorageKeys } from '../../../../services';
|
||||
|
@ -33,13 +34,14 @@ import {
|
|||
replaceRequestVariables,
|
||||
SELECTED_REQUESTS_CLASSNAME,
|
||||
shouldTriggerSuggestions,
|
||||
stringifyRequest,
|
||||
trackSentRequests,
|
||||
getRequestFromEditor,
|
||||
} from './utils';
|
||||
|
||||
import type { AdjustedParsedRequest } from './types';
|
||||
import { StorageQuotaError } from '../../../components/storage_quota_error';
|
||||
import { ContextValue } from '../../../contexts';
|
||||
import { containsComments, indentData } from './utils/requests_utils';
|
||||
|
||||
const AUTO_INDENTATION_ACTION_LABEL = 'Apply indentations';
|
||||
const TRIGGER_SUGGESTIONS_ACTION_LABEL = 'Trigger suggestions';
|
||||
|
@ -48,6 +50,7 @@ const DEBOUNCE_HIGHLIGHT_WAIT_MS = 200;
|
|||
const DEBOUNCE_AUTOCOMPLETE_WAIT_MS = 500;
|
||||
const INSPECT_TOKENS_LABEL = 'Inspect tokens';
|
||||
const INSPECT_TOKENS_HANDLER_ID = 'editor.action.inspectTokens';
|
||||
const { collapseLiteralStrings } = XJson;
|
||||
|
||||
export class MonacoEditorActionsProvider {
|
||||
private parsedRequestsProvider: ConsoleParsedRequestsProvider;
|
||||
|
@ -173,12 +176,12 @@ export class MonacoEditorActionsProvider {
|
|||
const selectedRequests: AdjustedParsedRequest[] = [];
|
||||
for (const [index, parsedRequest] of parsedRequests.entries()) {
|
||||
const requestStartLineNumber = getRequestStartLineNumber(parsedRequest, model);
|
||||
const requestEndLineNumber = getRequestEndLineNumber(
|
||||
const requestEndLineNumber = getRequestEndLineNumber({
|
||||
parsedRequest,
|
||||
nextRequest: parsedRequests.at(index + 1),
|
||||
model,
|
||||
index,
|
||||
parsedRequests
|
||||
);
|
||||
startLineNumber,
|
||||
});
|
||||
if (requestStartLineNumber > endLineNumber) {
|
||||
// request is past the selection, no need to check further requests
|
||||
break;
|
||||
|
@ -198,13 +201,31 @@ export class MonacoEditorActionsProvider {
|
|||
}
|
||||
|
||||
public async getRequests() {
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parsedRequests = await this.getSelectedParsedRequests();
|
||||
const stringifiedRequests = parsedRequests.map((parsedRequest) =>
|
||||
stringifyRequest(parsedRequest)
|
||||
);
|
||||
const stringifiedRequests = parsedRequests.map((parsedRequest) => {
|
||||
const { startLineNumber, endLineNumber } = parsedRequest;
|
||||
const requestTextFromEditor = getRequestFromEditor(model, startLineNumber, endLineNumber);
|
||||
if (requestTextFromEditor && requestTextFromEditor.data.length > 0) {
|
||||
requestTextFromEditor.data = requestTextFromEditor.data.map((dataString) => {
|
||||
if (containsComments(dataString)) {
|
||||
// parse and stringify to remove comments
|
||||
dataString = indentData(dataString);
|
||||
}
|
||||
return collapseLiteralStrings(dataString);
|
||||
});
|
||||
}
|
||||
return requestTextFromEditor;
|
||||
});
|
||||
// get variables values
|
||||
const variables = getStorage().get(StorageKeys.VARIABLES, DEFAULT_VARIABLES);
|
||||
return stringifiedRequests.map((request) => replaceRequestVariables(request, variables));
|
||||
return stringifiedRequests
|
||||
.filter(Boolean)
|
||||
.map((request) => replaceRequestVariables(request!, variables));
|
||||
}
|
||||
|
||||
public async getCurl(elasticsearchBaseUrl: string): Promise<string> {
|
||||
|
@ -388,12 +409,6 @@ export class MonacoEditorActionsProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
// if the current request doesn't have a method, the request is not valid
|
||||
// and shouldn't have an autocomplete type
|
||||
if (!currentRequest.method) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// if not on the 1st line of the request, suggest request body
|
||||
return AutocompleteType.BODY;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ export const getDocumentationLinkFromAutocomplete = (
|
|||
* Helper function that filters out suggestions without a name.
|
||||
*/
|
||||
const filterTermsWithoutName = (terms: ResultTerm[]): ResultTerm[] =>
|
||||
terms.filter((term) => term.name !== undefined);
|
||||
terms.filter((term) => term.name !== undefined && term.name !== '');
|
||||
|
||||
/*
|
||||
* This function returns an array of completion items for the request method
|
||||
|
|
|
@ -40,6 +40,10 @@ export const END_OF_URL_TOKEN = '__url_path_end__';
|
|||
* In this case autocomplete suggestions should be triggered for an url.
|
||||
*/
|
||||
export const methodWhitespaceRegex = /^\s*(GET|POST|PUT|PATCH|DELETE)\s+$/i;
|
||||
/*
|
||||
* This regex matches a string that starts with a method (optional whitespace before the method)
|
||||
*/
|
||||
export const startsWithMethodRegex = /^\s*(GET|POST|PUT|PATCH|DELETE)/i;
|
||||
/*
|
||||
* This regex matches a string that has
|
||||
* a method and some parts of an url ending with a slash, a question mark or an equals sign,
|
||||
|
|
|
@ -18,11 +18,11 @@ export {
|
|||
export {
|
||||
getRequestStartLineNumber,
|
||||
getRequestEndLineNumber,
|
||||
stringifyRequest,
|
||||
replaceRequestVariables,
|
||||
getCurlRequest,
|
||||
trackSentRequests,
|
||||
getAutoIndentedRequests,
|
||||
getRequestFromEditor,
|
||||
} from './requests_utils';
|
||||
export {
|
||||
getDocumentationLinkFromAutocomplete,
|
||||
|
|
|
@ -6,14 +6,16 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { monaco, ParsedRequest } from '@kbn/monaco';
|
||||
import type { MetricsTracker } from '../../../../../types';
|
||||
import {
|
||||
getAutoIndentedRequests,
|
||||
getCurlRequest,
|
||||
getRequestEndLineNumber,
|
||||
replaceRequestVariables,
|
||||
stringifyRequest,
|
||||
trackSentRequests,
|
||||
getRequestFromEditor,
|
||||
} from './requests_utils';
|
||||
import { MetricsTracker } from '../../../../../types';
|
||||
|
||||
describe('requests_utils', () => {
|
||||
const dataObjects = [
|
||||
|
@ -26,35 +28,23 @@ describe('requests_utils', () => {
|
|||
test: 'test',
|
||||
},
|
||||
];
|
||||
describe('stringifyRequest', () => {
|
||||
const request = {
|
||||
startOffset: 0,
|
||||
endOffset: 11,
|
||||
method: 'get',
|
||||
url: '_search some_text',
|
||||
};
|
||||
it('calls the "removeTrailingWhitespaces" on the url', () => {
|
||||
const stringifiedRequest = stringifyRequest(request);
|
||||
expect(stringifiedRequest.url).toBe('_search');
|
||||
});
|
||||
|
||||
it('normalizes the method to upper case', () => {
|
||||
const stringifiedRequest = stringifyRequest(request);
|
||||
expect(stringifiedRequest.method).toBe('GET');
|
||||
});
|
||||
it('stringifies the request body', () => {
|
||||
const result = stringifyRequest({ ...request, data: [dataObjects[0]] });
|
||||
expect(result.data.length).toBe(1);
|
||||
expect(result.data[0]).toBe(JSON.stringify(dataObjects[0], null, 2));
|
||||
});
|
||||
|
||||
it('works for several request bodies', () => {
|
||||
const result = stringifyRequest({ ...request, data: dataObjects });
|
||||
expect(result.data.length).toBe(2);
|
||||
expect(result.data[0]).toBe(JSON.stringify(dataObjects[0], null, 2));
|
||||
expect(result.data[1]).toBe(JSON.stringify(dataObjects[1], null, 2));
|
||||
});
|
||||
});
|
||||
const inlineData = '{"query":"test"}';
|
||||
const multiLineData = '{\n "query": "test"\n}';
|
||||
const invalidData = '{\n "query":\n {';
|
||||
const getMockModel = (content: string[]) => {
|
||||
return {
|
||||
getLineContent: (lineNumber: number) => content[lineNumber - 1],
|
||||
getValueInRange: ({
|
||||
startLineNumber,
|
||||
endLineNumber,
|
||||
}: {
|
||||
startLineNumber: number;
|
||||
endLineNumber: number;
|
||||
}) => content.slice(startLineNumber - 1, endLineNumber).join('\n'),
|
||||
getLineMaxColumn: (lineNumber: number) => content[lineNumber - 1].length,
|
||||
getLineCount: () => content.length,
|
||||
} as unknown as monaco.editor.ITextModel;
|
||||
};
|
||||
|
||||
describe('replaceRequestVariables', () => {
|
||||
const variables = [
|
||||
|
@ -213,9 +203,6 @@ describe('requests_utils', () => {
|
|||
];
|
||||
|
||||
const TEST_REQUEST_1 = {
|
||||
method: 'GET',
|
||||
url: '_search',
|
||||
data: [{ query: { match_all: {} } }],
|
||||
// Offsets are with respect to the sample editor text
|
||||
startLineNumber: 2,
|
||||
endLineNumber: 7,
|
||||
|
@ -224,9 +211,6 @@ describe('requests_utils', () => {
|
|||
};
|
||||
|
||||
const TEST_REQUEST_2 = {
|
||||
method: 'GET',
|
||||
url: '_all',
|
||||
data: [],
|
||||
// Offsets are with respect to the sample editor text
|
||||
startLineNumber: 10,
|
||||
endLineNumber: 10,
|
||||
|
@ -235,10 +219,6 @@ describe('requests_utils', () => {
|
|||
};
|
||||
|
||||
const TEST_REQUEST_3 = {
|
||||
method: 'POST',
|
||||
url: '/_bulk',
|
||||
// Multi-data
|
||||
data: [{ index: { _index: 'books' } }, { name: '1984' }, { name: 'Atomic habits' }],
|
||||
// Offsets are with respect to the sample editor text
|
||||
startLineNumber: 15,
|
||||
endLineNumber: 23,
|
||||
|
@ -247,11 +227,8 @@ describe('requests_utils', () => {
|
|||
};
|
||||
|
||||
const TEST_REQUEST_4 = {
|
||||
method: 'GET',
|
||||
url: '_search',
|
||||
data: [{ query: { match_all: {} } }],
|
||||
// Offsets are with respect to the sample editor text
|
||||
startLineNumber: 24,
|
||||
startLineNumber: 25,
|
||||
endLineNumber: 30,
|
||||
startOffset: 1,
|
||||
endOffset: 36,
|
||||
|
@ -353,17 +330,131 @@ describe('requests_utils', () => {
|
|||
expect(formattedData).toBe(expectedResultLines.join('\n'));
|
||||
});
|
||||
|
||||
it('does not auto-indent a request with comments', () => {
|
||||
const requestText = sampleEditorTextLines
|
||||
.slice(TEST_REQUEST_4.startLineNumber - 1, TEST_REQUEST_4.endLineNumber)
|
||||
it(`auto-indents method line but doesn't auto-indent data with comments`, () => {
|
||||
const methodLine = sampleEditorTextLines[TEST_REQUEST_4.startLineNumber - 1];
|
||||
const dataText = sampleEditorTextLines
|
||||
.slice(TEST_REQUEST_4.startLineNumber, TEST_REQUEST_4.endLineNumber)
|
||||
.join('\n');
|
||||
const formattedData = getAutoIndentedRequests(
|
||||
[TEST_REQUEST_4],
|
||||
requestText,
|
||||
`${methodLine}\n${dataText}`,
|
||||
sampleEditorTextLines.join('\n')
|
||||
);
|
||||
|
||||
expect(formattedData).toBe(requestText);
|
||||
expect(formattedData).toBe(`GET _search // test comment\n${dataText}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequestEndLineNumber', () => {
|
||||
const parsedRequest: ParsedRequest = {
|
||||
startOffset: 1,
|
||||
};
|
||||
it('detects the end of the request when there is a line that starts with a method (next not parsed request)', () => {
|
||||
/*
|
||||
* Mocking the model to return these 6 lines of text
|
||||
* 1. GET /_search
|
||||
* 2. {
|
||||
* 3. empty
|
||||
* 4. empty
|
||||
* 5. POST _search
|
||||
* 6. empty
|
||||
*/
|
||||
const content = ['GET /_search', '{', '', '', 'POST _search', ''];
|
||||
const model = {
|
||||
...getMockModel(content),
|
||||
getPositionAt: () => ({ lineNumber: 1 }),
|
||||
} as unknown as monaco.editor.ITextModel;
|
||||
|
||||
const result = getRequestEndLineNumber({
|
||||
parsedRequest,
|
||||
model,
|
||||
startLineNumber: 1,
|
||||
});
|
||||
expect(result).toEqual(2);
|
||||
});
|
||||
|
||||
it('detects the end of the request when the text ends', () => {
|
||||
/*
|
||||
* Mocking the model to return these 4 lines of text
|
||||
* 1. GET /_search
|
||||
* 2. {
|
||||
* 3. {
|
||||
* 4. empty
|
||||
*/
|
||||
const content = ['GET _search', '{', ' {', ''];
|
||||
const model = {
|
||||
...getMockModel(content),
|
||||
getPositionAt: () => ({ lineNumber: 1 }),
|
||||
} as unknown as monaco.editor.ITextModel;
|
||||
|
||||
const result = getRequestEndLineNumber({
|
||||
parsedRequest,
|
||||
model,
|
||||
startLineNumber: 1,
|
||||
});
|
||||
expect(result).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRequestFromEditor', () => {
|
||||
it('cleans up any text following the url', () => {
|
||||
const content = ['GET _search // inline comment'];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 1);
|
||||
expect(request).toEqual({ method: 'GET', url: '_search', data: [] });
|
||||
});
|
||||
|
||||
it(`doesn't incorrectly removes parts of url params that include whitespaces`, () => {
|
||||
const content = ['GET _search?query="test test"'];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 1);
|
||||
expect(request).toEqual({ method: 'GET', url: '_search?query="test test"', data: [] });
|
||||
});
|
||||
|
||||
it(`normalizes method to upper case`, () => {
|
||||
const content = ['get _search'];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 1);
|
||||
expect(request).toEqual({ method: 'GET', url: '_search', data: [] });
|
||||
});
|
||||
|
||||
it('correctly includes the request body', () => {
|
||||
const content = ['GET _search', '{', ' "query": {}', '}'];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 4);
|
||||
expect(request).toEqual({ method: 'GET', url: '_search', data: ['{\n "query": {}\n}'] });
|
||||
});
|
||||
|
||||
it('works for several request bodies', () => {
|
||||
const content = ['GET _search', '{', ' "query": {}', '}', '{', ' "query": {}', '}'];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 7);
|
||||
expect(request).toEqual({
|
||||
method: 'GET',
|
||||
url: '_search',
|
||||
data: ['{\n "query": {}\n}', '{\n "query": {}\n}'],
|
||||
});
|
||||
});
|
||||
|
||||
it('splits several json objects', () => {
|
||||
const content = ['GET _search', inlineData, ...multiLineData.split('\n'), inlineData];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 6);
|
||||
expect(request).toEqual({
|
||||
method: 'GET',
|
||||
url: '_search',
|
||||
data: [inlineData, multiLineData, inlineData],
|
||||
});
|
||||
});
|
||||
it('works for invalid json objects', () => {
|
||||
const content = ['GET _search', inlineData, ...invalidData.split('\n')];
|
||||
const model = getMockModel(content);
|
||||
const request = getRequestFromEditor(model, 1, 5);
|
||||
expect(request).toEqual({
|
||||
method: 'GET',
|
||||
url: '_search',
|
||||
data: [inlineData, invalidData],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,26 +7,17 @@
|
|||
*/
|
||||
|
||||
import { monaco, ParsedRequest } from '@kbn/monaco';
|
||||
import { parse } from 'hjson';
|
||||
import { constructUrl } from '../../../../../lib/es';
|
||||
import { MetricsTracker } from '../../../../../types';
|
||||
import type { MetricsTracker } from '../../../../../types';
|
||||
import type { DevToolsVariable } from '../../../../components';
|
||||
import type { EditorRequest } from '../types';
|
||||
import { urlVariableTemplateRegex, dataVariableTemplateRegex } from './constants';
|
||||
import { removeTrailingWhitespaces } from './tokens_utils';
|
||||
import { AdjustedParsedRequest } from '../types';
|
||||
|
||||
/*
|
||||
* This function stringifies and normalizes the parsed request:
|
||||
* - the method is converted to upper case
|
||||
* - any trailing comments are removed from the url
|
||||
* - the request body is stringified from an object using JSON.stringify
|
||||
*/
|
||||
export const stringifyRequest = (parsedRequest: ParsedRequest): EditorRequest => {
|
||||
const url = parsedRequest.url ? removeTrailingWhitespaces(parsedRequest.url) : '';
|
||||
const method = parsedRequest.method?.toUpperCase() ?? '';
|
||||
const data = parsedRequest.data?.map((parsedData) => JSON.stringify(parsedData, null, 2));
|
||||
return { url, method, data: data ?? [] };
|
||||
};
|
||||
import type { EditorRequest, AdjustedParsedRequest } from '../types';
|
||||
import {
|
||||
urlVariableTemplateRegex,
|
||||
dataVariableTemplateRegex,
|
||||
startsWithMethodRegex,
|
||||
} from './constants';
|
||||
import { parseLine } from './tokens_utils';
|
||||
|
||||
/*
|
||||
* This function replaces any variables with its values stored in localStorage.
|
||||
|
@ -52,9 +43,13 @@ export const getCurlRequest = (
|
|||
): string => {
|
||||
const curlUrl = constructUrl(elasticsearchBaseUrl, url);
|
||||
let curlRequest = `curl -X${method} "${curlUrl}" -H "kbn-xsrf: reporting"`;
|
||||
if (data.length > 0) {
|
||||
if (data && data.length) {
|
||||
const joinedData = data.join('\n');
|
||||
|
||||
curlRequest += ` -H "Content-Type: application/json" -d'\n`;
|
||||
curlRequest += data.join('\n');
|
||||
|
||||
// We escape single quoted strings that are wrapped in single quoted strings
|
||||
curlRequest += joinedData.replace(/'/g, "'\\''");
|
||||
curlRequest += "'";
|
||||
}
|
||||
return curlRequest;
|
||||
|
@ -88,25 +83,42 @@ export const getRequestStartLineNumber = (
|
|||
* If there is no end offset (the parser was not able to parse this request completely),
|
||||
* then the last non-empty line is returned or the line before the next request.
|
||||
*/
|
||||
export const getRequestEndLineNumber = (
|
||||
parsedRequest: ParsedRequest,
|
||||
model: monaco.editor.ITextModel,
|
||||
index: number,
|
||||
parsedRequests: ParsedRequest[]
|
||||
): number => {
|
||||
export const getRequestEndLineNumber = ({
|
||||
parsedRequest,
|
||||
nextRequest,
|
||||
model,
|
||||
startLineNumber,
|
||||
}: {
|
||||
parsedRequest: ParsedRequest;
|
||||
nextRequest?: ParsedRequest;
|
||||
model: monaco.editor.ITextModel;
|
||||
startLineNumber: number;
|
||||
}): number => {
|
||||
let endLineNumber: number;
|
||||
if (parsedRequest.endOffset) {
|
||||
// if the parser set an end offset for this request, then find the line number for it
|
||||
endLineNumber = model.getPositionAt(parsedRequest.endOffset).lineNumber;
|
||||
} else {
|
||||
// if no end offset, try to find the line before the next request starts
|
||||
const nextRequest = parsedRequests.at(index + 1);
|
||||
if (nextRequest) {
|
||||
const nextRequestStartLine = model.getPositionAt(nextRequest.startOffset).lineNumber;
|
||||
endLineNumber = nextRequestStartLine - 1;
|
||||
endLineNumber =
|
||||
nextRequestStartLine > startLineNumber ? nextRequestStartLine - 1 : startLineNumber;
|
||||
} else {
|
||||
// if there is no next request, take the last line of the model
|
||||
endLineNumber = model.getLineCount();
|
||||
// if there is no next request, find the end of the text or the line that starts with a method
|
||||
let nextLineNumber = model.getPositionAt(parsedRequest.startOffset).lineNumber + 1;
|
||||
let nextLineContent: string;
|
||||
while (nextLineNumber <= model.getLineCount()) {
|
||||
nextLineContent = model.getLineContent(nextLineNumber).trim();
|
||||
if (nextLineContent.match(startsWithMethodRegex)) {
|
||||
// found a line that starts with a method, stop iterating
|
||||
break;
|
||||
}
|
||||
nextLineNumber++;
|
||||
}
|
||||
// nextLineNumber is now either the line with a method or 1 line after the end of the text
|
||||
// set the end line for this request to the line before nextLineNumber
|
||||
endLineNumber = nextLineNumber > startLineNumber ? nextLineNumber - 1 : startLineNumber;
|
||||
}
|
||||
}
|
||||
// if the end line is empty, go up to find the first non-empty line
|
||||
|
@ -118,44 +130,6 @@ export const getRequestEndLineNumber = (
|
|||
return endLineNumber;
|
||||
};
|
||||
|
||||
const isJsonString = (str: string) => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
* Internal helpers
|
||||
*/
|
||||
const replaceVariables = (
|
||||
text: string,
|
||||
variables: DevToolsVariable[],
|
||||
isDataVariable: boolean
|
||||
): string => {
|
||||
const variableRegex = isDataVariable ? dataVariableTemplateRegex : urlVariableTemplateRegex;
|
||||
if (variableRegex.test(text)) {
|
||||
text = text.replaceAll(variableRegex, (match, key) => {
|
||||
const variable = variables.find(({ name }) => name === key);
|
||||
const value = variable?.value;
|
||||
|
||||
if (isDataVariable && value) {
|
||||
// If the variable value is an object, add it as it is. Otherwise, surround it with quotes.
|
||||
return isJsonString(value) ? value : `"${value}"`;
|
||||
}
|
||||
|
||||
return value ?? match;
|
||||
});
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const containsComments = (text: string) => {
|
||||
return text.indexOf('//') >= 0 || text.indexOf('/*') >= 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function takes a string containing unformatted Console requests and
|
||||
* returns a text in which the requests are auto-indented.
|
||||
|
@ -184,19 +158,19 @@ export const getAutoIndentedRequests = (
|
|||
) {
|
||||
// Start of a request
|
||||
const requestLines = allTextLines.slice(request.startLineNumber - 1, request.endLineNumber);
|
||||
|
||||
if (requestLines.some((line) => containsComments(line))) {
|
||||
// If request has comments, add it as it is - without formatting
|
||||
// TODO: Format requests with comments
|
||||
formattedTextLines.push(...requestLines);
|
||||
const firstLine = cleanUpWhitespaces(requestLines[0]);
|
||||
formattedTextLines.push(firstLine);
|
||||
const dataLines = requestLines.slice(1);
|
||||
if (dataLines.some((line) => containsComments(line))) {
|
||||
// If data has comments, add it as it is - without formatting
|
||||
// TODO: Format requests with comments https://github.com/elastic/kibana/issues/182138
|
||||
formattedTextLines.push(...dataLines);
|
||||
} else {
|
||||
// If no comments, add stringified parsed request
|
||||
const stringifiedRequest = stringifyRequest(request);
|
||||
const firstLine = stringifiedRequest.method + ' ' + stringifiedRequest.url;
|
||||
formattedTextLines.push(firstLine);
|
||||
|
||||
if (stringifiedRequest.data && stringifiedRequest.data.length > 0) {
|
||||
formattedTextLines.push(...stringifiedRequest.data);
|
||||
// If no comments, indent data
|
||||
if (requestLines.length > 1) {
|
||||
const dataString = dataLines.join('\n');
|
||||
const dataJsons = splitDataIntoJsonObjects(dataString);
|
||||
formattedTextLines.push(...dataJsons.map(indentData));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,10 +179,116 @@ export const getAutoIndentedRequests = (
|
|||
} else {
|
||||
// Current line is a comment or whitespaces
|
||||
// Trim white spaces and add it to the formatted text
|
||||
formattedTextLines.push(selectedTextLines[currentLineIndex].trim());
|
||||
formattedTextLines.push(cleanUpWhitespaces(selectedTextLines[currentLineIndex]));
|
||||
currentLineIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return formattedTextLines.join('\n');
|
||||
};
|
||||
|
||||
/*
|
||||
* This function extracts a normalized method and url from the editor and
|
||||
* the "raw" text of the request body without any changes to it. The only normalization
|
||||
* for request body is to split several json objects into an array of strings.
|
||||
*/
|
||||
export const getRequestFromEditor = (
|
||||
model: monaco.editor.ITextModel,
|
||||
startLineNumber: number,
|
||||
endLineNumber: number
|
||||
): EditorRequest | null => {
|
||||
const methodUrlLine = model.getLineContent(startLineNumber).trim();
|
||||
if (!methodUrlLine) {
|
||||
return null;
|
||||
}
|
||||
const { method, url } = parseLine(methodUrlLine, false);
|
||||
if (!method || !url) {
|
||||
return null;
|
||||
}
|
||||
const upperCaseMethod = method.toUpperCase();
|
||||
|
||||
if (endLineNumber <= startLineNumber) {
|
||||
return { method: upperCaseMethod, url, data: [] };
|
||||
}
|
||||
const dataString = model
|
||||
.getValueInRange({
|
||||
startLineNumber: startLineNumber + 1,
|
||||
startColumn: 1,
|
||||
endLineNumber,
|
||||
endColumn: model.getLineMaxColumn(endLineNumber),
|
||||
})
|
||||
.trim();
|
||||
const data = splitDataIntoJsonObjects(dataString);
|
||||
|
||||
return { method: upperCaseMethod, url, data };
|
||||
};
|
||||
|
||||
export const containsComments = (text: string) => {
|
||||
return text.indexOf('//') >= 0 || text.indexOf('/*') >= 0;
|
||||
};
|
||||
|
||||
export const indentData = (dataString: string): string => {
|
||||
try {
|
||||
const parsedData = parse(dataString);
|
||||
|
||||
return JSON.stringify(parsedData, null, 2);
|
||||
} catch (e) {
|
||||
return dataString;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------- Internal helpers ----------------------------------
|
||||
|
||||
const isJsonString = (str: string) => {
|
||||
try {
|
||||
JSON.parse(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const replaceVariables = (
|
||||
text: string,
|
||||
variables: DevToolsVariable[],
|
||||
isDataVariable: boolean
|
||||
): string => {
|
||||
const variableRegex = isDataVariable ? dataVariableTemplateRegex : urlVariableTemplateRegex;
|
||||
if (variableRegex.test(text)) {
|
||||
text = text.replaceAll(variableRegex, (match, key) => {
|
||||
const variable = variables.find(({ name }) => name === key);
|
||||
const value = variable?.value;
|
||||
|
||||
if (isDataVariable && value) {
|
||||
// If the variable value is an object, add it as it is. Otherwise, surround it with quotes.
|
||||
return isJsonString(value) ? value : `"${value}"`;
|
||||
}
|
||||
|
||||
return value ?? match;
|
||||
});
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const splitDataIntoJsonObjects = (dataString: string): string[] => {
|
||||
const jsonSplitRegex = /}\s*{/;
|
||||
if (dataString.match(jsonSplitRegex)) {
|
||||
return dataString.split(jsonSplitRegex).map((part, index, parts) => {
|
||||
let restoredBracketsString = part;
|
||||
// add an opening bracket to all parts except the 1st
|
||||
if (index > 0) {
|
||||
restoredBracketsString = `{${restoredBracketsString}`;
|
||||
}
|
||||
// add a closing bracket to all parts except the last
|
||||
if (index < parts.length - 1) {
|
||||
restoredBracketsString = `${restoredBracketsString}}`;
|
||||
}
|
||||
return restoredBracketsString;
|
||||
});
|
||||
}
|
||||
return [dataString];
|
||||
};
|
||||
|
||||
const cleanUpWhitespaces = (line: string): string => {
|
||||
return line.trim().replaceAll(/\s+/g, ' ');
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { parseBody, removeTrailingWhitespaces, parseUrl } from './tokens_utils';
|
||||
import { parseBody, removeTrailingWhitespaces, parseUrl, parseLine } from './tokens_utils';
|
||||
|
||||
describe('tokens_utils', () => {
|
||||
describe('removeTrailingWhitespaces', () => {
|
||||
|
@ -32,6 +32,53 @@ describe('tokens_utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('parseLine', () => {
|
||||
it('works with a comment', () => {
|
||||
const { method, url } = parseLine('GET _search // a comment');
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_search');
|
||||
});
|
||||
it('works with a url param', () => {
|
||||
const { method, url, urlPathTokens, urlParamsTokens } = parseLine(
|
||||
'GET _search?query="test1 test2 test3" // comment'
|
||||
);
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_search?query="test1 test2 test3"');
|
||||
expect(urlPathTokens).toEqual(['_search']);
|
||||
expect(urlParamsTokens[0]).toEqual(['query', '"test1 test2 test3"']);
|
||||
});
|
||||
it('works with multiple whitespaces', () => {
|
||||
const { method, url, urlPathTokens, urlParamsTokens } = parseLine(
|
||||
' GET _search?query="test1 test2 test3" // comment'
|
||||
);
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_search?query="test1 test2 test3"');
|
||||
expect(urlPathTokens).toEqual(['_search']);
|
||||
expect(urlParamsTokens[0]).toEqual(['query', '"test1 test2 test3"']);
|
||||
});
|
||||
it('normalizes the method to upper case', () => {
|
||||
const { method, url, urlPathTokens, urlParamsTokens } = parseLine('Get _');
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('_');
|
||||
expect(urlPathTokens).toEqual(['_']);
|
||||
expect(urlParamsTokens).toEqual([]);
|
||||
});
|
||||
it('correctly parses the line when the url is empty, no whitespace', () => {
|
||||
const { method, url, urlPathTokens, urlParamsTokens } = parseLine('GET');
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('');
|
||||
expect(urlPathTokens).toEqual([]);
|
||||
expect(urlParamsTokens).toEqual([]);
|
||||
});
|
||||
it('correctly parses the line when the url is empty, with whitespace', () => {
|
||||
const { method, url, urlPathTokens, urlParamsTokens } = parseLine('GET ');
|
||||
expect(method).toBe('GET');
|
||||
expect(url).toBe('');
|
||||
expect(urlPathTokens).toEqual([]);
|
||||
expect(urlParamsTokens).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBody', () => {
|
||||
const testCases: Array<{ value: string; tokens: string[] }> = [
|
||||
{
|
||||
|
|
|
@ -19,18 +19,25 @@ import {
|
|||
/*
|
||||
* This function parses a line with the method and url.
|
||||
* The url is parsed into path and params, each parsed into tokens.
|
||||
* Returns method, urlPathTokens and urlParamsTokens which are arrays of strings.
|
||||
* Returns method, url, urlPathTokens and urlParamsTokens which are arrays of strings.
|
||||
*/
|
||||
export const parseLine = (line: string): ParsedLineTokens => {
|
||||
// try to parse into method and url (split on whitespace)
|
||||
const parts = line.split(whitespacesRegex);
|
||||
export const parseLine = (line: string, parseUrlIntoTokens: boolean = true): ParsedLineTokens => {
|
||||
line = line.trim();
|
||||
const firstWhitespaceIndex = line.indexOf(' ');
|
||||
if (firstWhitespaceIndex < 0) {
|
||||
// there is no url, only method
|
||||
return { method: line, url: '', urlPathTokens: [], urlParamsTokens: [] };
|
||||
}
|
||||
// 1st part is the method
|
||||
const method = parts[0].toUpperCase();
|
||||
const method = line.slice(0, firstWhitespaceIndex).trim().toUpperCase();
|
||||
// 2nd part is the url
|
||||
const url = parts[1];
|
||||
// try to parse into url path and url params (split on question mark)
|
||||
const { urlPathTokens, urlParamsTokens } = parseUrl(url);
|
||||
return { method, urlPathTokens, urlParamsTokens };
|
||||
const url = removeTrailingWhitespaces(line.slice(firstWhitespaceIndex).trim());
|
||||
if (parseUrlIntoTokens) {
|
||||
// try to parse into url path and url params (split on question mark)
|
||||
const { urlPathTokens, urlParamsTokens } = parseUrl(url);
|
||||
return { method, url, urlPathTokens, urlParamsTokens };
|
||||
}
|
||||
return { method, url, urlPathTokens: [], urlParamsTokens: [] };
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -444,6 +451,7 @@ export const containsUrlParams = (lineContent: string): boolean => {
|
|||
*/
|
||||
interface ParsedLineTokens {
|
||||
method: string;
|
||||
url: string;
|
||||
urlPathTokens: string[];
|
||||
urlParamsTokens: string[][];
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const log = getService('log');
|
||||
const toasts = getService('toasts');
|
||||
const browser = getService('browser');
|
||||
const PageObjects = getPageObjects(['common', 'console', 'header']);
|
||||
const security = getService('security');
|
||||
|
@ -58,12 +57,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
expect(initialSize.width).to.be.greaterThan(afterSize.width);
|
||||
});
|
||||
|
||||
it('should not send request with unsupported HTTP verbs', async () => {
|
||||
it('should return statusCode 400 to unsupported HTTP verbs', async () => {
|
||||
const expectedResponseContains = '"statusCode": 400';
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('OPTIONS /');
|
||||
await PageObjects.console.clickPlay();
|
||||
await retry.try(async () => {
|
||||
expect(await toasts.getCount()).to.equal(1);
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
log.debug(actualResponse);
|
||||
expect(actualResponse).to.contain(expectedResponseContains);
|
||||
|
||||
expect(await PageObjects.console.hasSuccessBadge()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -133,15 +133,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await browser.switchTab(0);
|
||||
});
|
||||
|
||||
// not implemented yet for monaco https://github.com/elastic/kibana/issues/185891
|
||||
it.skip('should toggle auto indent when auto indent button is clicked', async () => {
|
||||
await PageObjects.console.clearTextArea();
|
||||
await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}');
|
||||
it('should auto indent when auto indent button is clicked', async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}');
|
||||
await PageObjects.console.clickContextMenu();
|
||||
await PageObjects.console.clickAutoIndentButton();
|
||||
// Retry until the request is auto indented
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.getRequest();
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}');
|
||||
});
|
||||
});
|
||||
|
||||
// not implemented for monaco yet https://github.com/elastic/kibana/issues/185891
|
||||
it.skip('should collapse the request when auto indent button is clicked again', async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}');
|
||||
await PageObjects.console.clickContextMenu();
|
||||
await PageObjects.console.clickAutoIndentButton();
|
||||
// Retry until the request is auto indented
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}');
|
||||
});
|
||||
|
||||
|
@ -150,7 +162,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await PageObjects.console.clickAutoIndentButton();
|
||||
// Retry until the request is condensed
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.getRequest();
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -147,5 +147,34 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid requests', () => {
|
||||
const invalidRequestText = 'GET _search\n{"query": {"match_all": {';
|
||||
it(`should not delete any text if indentations applied to an invalid request`, async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText(invalidRequestText);
|
||||
await PageObjects.console.monaco.selectCurrentRequest();
|
||||
await PageObjects.console.monaco.pressCtrlI();
|
||||
// Sleep for a bit and then check that the text has not changed
|
||||
await PageObjects.common.sleep(1000);
|
||||
await retry.try(async () => {
|
||||
const request = await PageObjects.console.monaco.getEditorText();
|
||||
expect(request).to.be.eql(invalidRequestText);
|
||||
});
|
||||
});
|
||||
|
||||
it(`should include an invalid json when sending a request`, async () => {
|
||||
await PageObjects.console.monaco.clearEditorText();
|
||||
await PageObjects.console.monaco.enterText(invalidRequestText);
|
||||
await PageObjects.console.monaco.selectCurrentRequest();
|
||||
await PageObjects.console.monaco.pressCtrlEnter();
|
||||
|
||||
await retry.try(async () => {
|
||||
const actualResponse = await PageObjects.console.monaco.getOutputText();
|
||||
expect(actualResponse).to.contain('parsing_exception');
|
||||
expect(await PageObjects.console.hasSuccessBadge()).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue