Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Console Migration] Add action for auto indentation #181613

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/kbn-monaco/src/console/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const createParser = () => {
},
addRequestEnd = function() {
const lastRequest = getLastRequest();
const requestText = text.substring(requestStartOffset, requestEndOffset);
lastRequest.endOffset = requestEndOffset;
lastRequest.text = requestText;
requests.push(lastRequest);
},
error = function (m) {
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-monaco/src/console/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ParsedRequest {
method: string;
url: string;
data?: Array<Record<string, unknown>>;
text: string;
}
export interface ConsoleParserResult {
errors: ErrorAnnotation[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
return actionsProvider.current!.getDocumentationLink(docLinkVersion);
}, [docLinkVersion]);

const autoIndentCallback = useCallback(async (event: React.MouseEvent) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the argument "event" is now unused and can be deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I removed it in 3e1264f.

return actionsProvider.current!.autoIndent(event);
}, []);

const sendRequestsCallback = useCallback(async () => {
await actionsProvider.current?.sendRequests(toasts, dispatch, trackUiMetric, http);
}, [dispatch, http, toasts, trackUiMetric]);
Expand Down Expand Up @@ -123,7 +127,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => {
<ConsoleMenu
getCurl={getCurlCallback}
getDocumentation={getDocumenationLink}
autoIndent={() => {}}
autoIndent={autoIndentCallback}
notifications={notifications}
/>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
replaceRequestVariables,
stringifyRequest,
trackSentRequests,
getAutoIndentedRequests,
} from './utils';

const selectedRequestsClass = 'console__monaco_editor__selectedRequests';
Expand All @@ -43,9 +44,10 @@ export interface EditorRequest {
method: string;
url: string;
data: string[];
text: string;
}

interface AdjustedParsedRequest extends ParsedRequest {
export interface AdjustedParsedRequest extends ParsedRequest {
startLineNumber: number;
endLineNumber: number;
}
Expand Down Expand Up @@ -340,4 +342,47 @@ export class MonacoEditorActionsProvider {
): monaco.languages.ProviderResult<monaco.languages.CompletionList> {
return this.getSuggestions(model, position);
}

private getTextInRange(selectionRange: monaco.IRange): string {
const model = this.editor.getModel();
if (!model || !selectionRange) {
return '';
}
const { startLineNumber, startColumn, endLineNumber, endColumn } = selectionRange;
const text = model.getValueInRange({
startLineNumber,
startColumn,
endLineNumber,
endColumn,
});
return text;
}

public async autoIndent(event: React.MouseEvent) {
event.preventDefault();
ElenaStoeva marked this conversation as resolved.
Show resolved Hide resolved
const parsedRequests = await this.getSelectedParsedRequests();
const selectionStartLineNumber = parsedRequests[0].startLineNumber;
const selectionEndLineNumber = parsedRequests[parsedRequests.length - 1].endLineNumber;
const selectedRange = new monaco.Range(
selectionStartLineNumber,
1,
selectionEndLineNumber,
this.editor.getModel()?.getLineMaxColumn(selectionEndLineNumber) ?? 1
);

if (parsedRequests.length < 1) {
return;
}

const selectedText = this.getTextInRange(selectedRange);

const autoIndentedText = getAutoIndentedRequests(parsedRequests, selectedText);

this.editor.executeEdits('', [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think for completeness sake we should use some kind of string to indicate that the edit was made by the auto-indent button

Copy link
Contributor Author

@ElenaStoeva ElenaStoeva May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I wasn't sure how this string would be used... will it be visible to the user somehow or do you think we'll only need it for internal use? I added a translated string (in case it will be displayed to the user), let me know what you think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the string might be used for undo/redo actions which I don't think we currently have. I believe the value won't be displayed in the UI to the user though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see... Then it might be good to store this in a constant in case it's needed somewhere. I added this change with 3e1264f.

{
range: selectedRange,
text: autoIndentedText,
},
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
*/

import {
getAutoIndentedRequests,
getCurlRequest,
getDocumentationLink,
removeTrailingWhitespaces,
replaceRequestVariables,
stringifyRequest,
trackSentRequests,
isStartOfRequest,
} from './utils';
import { MetricsTracker } from '../../../../types';
import { AutoCompleteContext } from '../../../../lib/autocomplete/types';
Expand Down Expand Up @@ -65,6 +67,7 @@ describe('monaco editor utils', () => {
endOffset: 11,
method: 'get',
url: '_search some_text',
text: 'get _search some_text',
};
it('calls the "removeTrailingWhitespaces" on the url', () => {
const stringifiedRequest = stringifyRequest(request);
Expand Down Expand Up @@ -104,6 +107,7 @@ describe('monaco editor utils', () => {
method: 'GET',
url: '${variable1}',
data: [],
text: 'GET ${variable1}',
};
it('when there is no other text', () => {
const result = replaceRequestVariables(request, variables);
Expand All @@ -130,6 +134,7 @@ describe('monaco editor utils', () => {
method: 'GET',
url: '${variable1}',
data: [JSON.stringify({ '${variable1}': '${variable2}' }, null, 2)],
text: '',
};
it('works with several variables', () => {
const result = replaceRequestVariables(request, variables);
Expand All @@ -140,7 +145,7 @@ describe('monaco editor utils', () => {

describe('getCurlRequest', () => {
it('works without a request body', () => {
const request = { method: 'GET', url: '_search', data: [] };
const request = { method: 'GET', url: '_search', data: [], text: '' };
const result = getCurlRequest(request, 'http://test.com');
expect(result).toBe('curl -XGET "http://test.com/_search" -H "kbn-xsrf: reporting"');
});
Expand All @@ -149,6 +154,7 @@ describe('monaco editor utils', () => {
method: 'GET',
url: '_search',
data: [JSON.stringify(dataObjects[0], null, 2)],
text: '',
};
const result = getCurlRequest(request, 'http://test.com');
expect(result).toBe(
Expand All @@ -165,6 +171,7 @@ describe('monaco editor utils', () => {
method: 'GET',
url: '_search',
data: [JSON.stringify(dataObjects[0], null, 2), JSON.stringify(dataObjects[1], null, 2)],
text: '',
};
const result = getCurlRequest(request, 'http://test.com');
expect(result).toBe(
Expand All @@ -184,8 +191,8 @@ describe('monaco editor utils', () => {
describe('trackSentRequests', () => {
it('tracks each request correctly', () => {
const requests = [
{ method: 'GET', url: '_search', data: [] },
{ method: 'POST', url: '_test', data: [] },
{ method: 'GET', url: '_search', data: [], text: '' },
{ method: 'POST', url: '_test', data: [], text: '' },
];
const mockMetricsTracker: jest.Mocked<MetricsTracker> = { count: jest.fn(), load: jest.fn() };
trackSentRequests(requests, mockMetricsTracker);
Expand All @@ -196,7 +203,7 @@ describe('monaco editor utils', () => {
});

describe('getDocumentationLink', () => {
const mockRequest = { method: 'GET', url: '_search', data: [] };
const mockRequest = { method: 'GET', url: '_search', data: [], text: '' };
const version = '8.13';
const expectedLink = 'http://elastic.co/8.13/_search';

Expand Down Expand Up @@ -239,4 +246,131 @@ describe('monaco editor utils', () => {
expect(link).toBe(expectedLink);
});
});

describe('isStartOfRequest', () => {
it('correctly matches first lines of requests', () => {
expect(isStartOfRequest('GET _all')).toBe(true);
expect(isStartOfRequest(' get _search ')).toBe(true);
expect(isStartOfRequest('POST _all')).toBe(true);
expect(isStartOfRequest(' post _search ')).toBe(true);
expect(isStartOfRequest('PUT _all')).toBe(true);
expect(isStartOfRequest(' put _search ')).toBe(true);
expect(isStartOfRequest('DELETE _all')).toBe(true);
expect(isStartOfRequest(' delete _search ')).toBe(true);
expect(isStartOfRequest('HEAD _all')).toBe(true);
expect(isStartOfRequest(' head _search ')).toBe(true);
expect(isStartOfRequest('PATCH _all')).toBe(true);
expect(isStartOfRequest(' patch _search ')).toBe(true);
});

it('does not match any other lines', () => {
expect(isStartOfRequest('// comment')).toBe(false);
expect(isStartOfRequest('/*')).toBe(false);
expect(isStartOfRequest(' { ')).toBe(false);
expect(isStartOfRequest('"jdks": 4')).toBe(false);
expect(isStartOfRequest(' ')).toBe(false);
expect(isStartOfRequest('')).toBe(false);
expect(isStartOfRequest(' }')).toBe(false);
});
});

describe('getAutoIndentedRequests', () => {
const mockRequestParams = {
// Required properties in the AdjustedParsedRequest type
startLineNumber: 1,
endLineNumber: 1,
startOffset: 1,
endOffset: 1,
};

const TEST_REQUEST_1 = {
...mockRequestParams,
method: 'GET',
url: '_search',
data: [{ query: { match_all: {} } }],
// Non-formatted text
text: 'GET _search \n{ \n "query": {\n "match_all": { }\n }\n}',
};

const TEST_REQUEST_2 = {
...mockRequestParams,
method: 'GET',
url: '_all',
data: [],
// Non-formatted text
text: ' GET _all ',
};

const TEST_REQUEST_3 = {
...mockRequestParams,
method: 'POST',
url: '/_bulk',
// Multi-data
data: [{ index: { _index: 'books' } }, { name: '1984' }, { name: 'Atomic habits' }],
// Non-formatted text
text: 'POST /_bulk\n{\n"index":{\n"_index":"books"\n}\n}\n{\n"name":"1984"\n}{"name":"Atomic habits"}',
};

const TEST_REQUEST_4 = {
...mockRequestParams,
method: 'GET',
url: '_search',
data: [{ query: { match_all: {} } }],
// Non-formatted text with comments
text: 'GET _search // test comment \n{ \n "query": {\n "match_all": { } // comment\n }\n}',
};

it('correctly auto-indents a single request with data', () => {
const formattedData = getAutoIndentedRequests([TEST_REQUEST_1], TEST_REQUEST_1.text);
const expectedResult = 'GET _search\n{\n "query": {\n "match_all": {}\n }\n}';
expect(formattedData).toBe(expectedResult);
});

it('correctly auto-indents a single request with no data', () => {
const formattedData = getAutoIndentedRequests([TEST_REQUEST_2], TEST_REQUEST_2.text);
const expectedResult = 'GET _all';

expect(formattedData).toBe(expectedResult);
});

it('correctly auto-indents a single request with multiple data', () => {
const formattedData = getAutoIndentedRequests([TEST_REQUEST_3], TEST_REQUEST_3.text);
const expectedResult =
'POST /_bulk\n{\n "index": {\n "_index": "books"\n }\n}\n{\n "name": "1984"\n}\n{\n "name": "Atomic habits"\n}';

expect(formattedData).toBe(expectedResult);
});

it('correctly auto-indents multiple request', () => {
const formattedData = getAutoIndentedRequests(
[TEST_REQUEST_1, TEST_REQUEST_2, TEST_REQUEST_3],
TEST_REQUEST_1.text + '\n\n' + TEST_REQUEST_2.text + '\n\n' + TEST_REQUEST_3.text
);
const expectedResult =
'GET _search\n{\n "query": {\n "match_all": {}\n }\n}\n\nGET _all\n\nPOST /_bulk\n{\n "index": {\n "_index": "books"\n }\n}\n{\n "name": "1984"\n}\n{\n "name": "Atomic habits"\n}';

expect(formattedData).toBe(expectedResult);
});

it('auto-indents multiple request with comments in between', () => {
const formattedData = getAutoIndentedRequests(
[TEST_REQUEST_1, TEST_REQUEST_2, TEST_REQUEST_3],
TEST_REQUEST_1.text +
'\n\n// single comment\n' +
TEST_REQUEST_2.text +
'\n\n/*\n multi-line comment\n*/\n' +
TEST_REQUEST_3.text
);
const expectedResult =
'GET _search\n{\n "query": {\n "match_all": {}\n }\n}\n\n// single comment\nGET _all\n\n/*\n multi-line comment\n*/\nPOST /_bulk\n{\n "index": {\n "_index": "books"\n }\n}\n{\n "name": "1984"\n}\n{\n "name": "Atomic habits"\n}';

expect(formattedData).toBe(expectedResult);
});

it('does not auto-indent a request with comments', () => {
const formattedData = getAutoIndentedRequests([TEST_REQUEST_4], TEST_REQUEST_4.text);
const expectedResult = TEST_REQUEST_4.text;
expect(formattedData).toBe(expectedResult);
});
});
});
Loading