Skip to content

Commit 214d12f

Browse files
[8.19] [Console] Fix auto-indentation of requests with triple quotes (elastic#218305) (elastic#220679)
# Backport This will backport the following commits from `main` to `8.19`: - [[Console] Fix auto-indentation of requests with triple quotes (elastic#218305)](elastic#218305) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Elena Stoeva","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-05-09T15:11:25Z","message":"[Console] Fix auto-indentation of requests with triple quotes (elastic#218305)\n\nFixes https://github.com/elastic/kibana/issues/217966\n\n## Summary\n\nThis PR fixes auto-indentation of requests that contain triple-quote\nstrings.\n\nSample request to test with:\n\n```\nPOST /_query\n{\n \"query\": \"\"\"\n FROM sample_data\n | WHERE message LIKE \"Connected*\"\n | SORT @timestamp DESC\n \"\"\"\n}\n```\n\n\n\nhttps://github.com/user-attachments/assets/e62caba9-4c9f-4120-b2b1-0faaa9bc9beb","sha":"094b5d59c4e1757c63c9fb4a429f26f264a00cc1","branchLabelMapping":{"^v9.1.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Console","Team:Kibana Management","release_note:skip","backport:prev-minor","v9.1.0","v8.19.0"],"title":"[Console] Fix auto-indentation of requests with triple quotes","number":218305,"url":"https://github.com/elastic/kibana/pull/218305","mergeCommit":{"message":"[Console] Fix auto-indentation of requests with triple quotes (elastic#218305)\n\nFixes https://github.com/elastic/kibana/issues/217966\n\n## Summary\n\nThis PR fixes auto-indentation of requests that contain triple-quote\nstrings.\n\nSample request to test with:\n\n```\nPOST /_query\n{\n \"query\": \"\"\"\n FROM sample_data\n | WHERE message LIKE \"Connected*\"\n | SORT @timestamp DESC\n \"\"\"\n}\n```\n\n\n\nhttps://github.com/user-attachments/assets/e62caba9-4c9f-4120-b2b1-0faaa9bc9beb","sha":"094b5d59c4e1757c63c9fb4a429f26f264a00cc1"}},"sourceBranch":"main","suggestedTargetBranches":["8.19"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/218305","number":218305,"mergeCommit":{"message":"[Console] Fix auto-indentation of requests with triple quotes (elastic#218305)\n\nFixes https://github.com/elastic/kibana/issues/217966\n\n## Summary\n\nThis PR fixes auto-indentation of requests that contain triple-quote\nstrings.\n\nSample request to test with:\n\n```\nPOST /_query\n{\n \"query\": \"\"\"\n FROM sample_data\n | WHERE message LIKE \"Connected*\"\n | SORT @timestamp DESC\n \"\"\"\n}\n```\n\n\n\nhttps://github.com/user-attachments/assets/e62caba9-4c9f-4120-b2b1-0faaa9bc9beb","sha":"094b5d59c4e1757c63c9fb4a429f26f264a00cc1"}},{"branch":"8.19","label":"v8.19.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Elena Stoeva <[email protected]>
1 parent 17495ad commit 214d12f

File tree

2 files changed

+149
-5
lines changed

2 files changed

+149
-5
lines changed

src/platform/plugins/shared/console/public/application/containers/editor/utils/requests_utils.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
trackSentRequests,
1818
getRequestFromEditor,
1919
containsComments,
20+
collapseTripleQuoteStrings,
21+
expandTripleQuoteStrings,
22+
TRIPLE_QUOTE_STRINGS_MARKER,
2023
} from './requests_utils';
2124

2225
describe('requests_utils', () => {
@@ -203,6 +206,14 @@ describe('requests_utils', () => {
203206
'} ', // line 30
204207
' // some comment ', // line 31
205208
' ', // line 32
209+
'POST _query ', // line 33
210+
'{ ', // line 34
211+
' "query": """', // line 35
212+
' FROM sample_data', // line 36
213+
' | WHERE message LIKE "Connected *"', // line 37
214+
' | SORT @timestamp DESC', // line 38
215+
' """ ', // line 39
216+
'} ', // line 40
206217
];
207218

208219
const TEST_REQUEST_1 = {
@@ -237,6 +248,18 @@ describe('requests_utils', () => {
237248
endOffset: 36,
238249
};
239250

251+
const TEST_REQUEST_5 = {
252+
// Offsets are with respect to the sample editor text
253+
startLineNumber: 33,
254+
endLineNumber: 40,
255+
startOffset: 1,
256+
endOffset: 36,
257+
};
258+
259+
afterEach(() => {
260+
mockAddToastWarning.mockClear();
261+
});
262+
240263
it('correctly auto-indents a single request with data', () => {
241264
const formattedData = getAutoIndentedRequests(
242265
[TEST_REQUEST_1],
@@ -359,6 +382,31 @@ describe('requests_utils', () => {
359382
'Auto-indentation is currently not supported for requests containing comments. Please remove comments to enable formatting.'
360383
)
361384
);
385+
mockAddToastWarning.mockReset();
386+
});
387+
388+
it('correctly auto-indents a single request that contains triple quotes', () => {
389+
const formattedData = getAutoIndentedRequests(
390+
[TEST_REQUEST_5],
391+
sampleEditorTextLines
392+
.slice(TEST_REQUEST_5.startLineNumber - 1, TEST_REQUEST_5.endLineNumber)
393+
.join('\n'),
394+
sampleEditorTextLines.join('\n'),
395+
mockAddToastWarning
396+
);
397+
const expectedResultLines = [
398+
'POST _query',
399+
'{',
400+
' "query": """',
401+
' FROM sample_data',
402+
' | WHERE message LIKE "Connected *"',
403+
' | SORT @timestamp DESC',
404+
' """',
405+
'}',
406+
];
407+
408+
expect(formattedData).toBe(expectedResultLines.join('\n'));
409+
expect(mockAddToastWarning).not.toHaveBeenCalled();
362410
});
363411
});
364412

@@ -556,4 +604,49 @@ describe('requests_utils', () => {
556604
expect(containsComments(requestData)).toBe(true);
557605
});
558606
});
607+
608+
describe('collapseTripleQuoteStrings and expandTripleQuoteStrings', () => {
609+
const input = `{
610+
"query1": """FROM sample_data | LIMIT 3""",
611+
"query2": """
612+
FROM sample_data
613+
| WHERE message LIKE "Connected*"
614+
| SORT @timestamp DESC
615+
"""
616+
}`;
617+
618+
it('should collapse and re-expand both inline and multi-line triple-quote strings correctly', () => {
619+
const { collapsedTripleQuotesData, tripleQuoteStrings } = collapseTripleQuoteStrings(input);
620+
621+
// Validate that both triple-quoted strings were replaced with the marker
622+
expect(collapsedTripleQuotesData).toBe(`{
623+
"query1": ${TRIPLE_QUOTE_STRINGS_MARKER},
624+
"query2": ${TRIPLE_QUOTE_STRINGS_MARKER}
625+
}`);
626+
627+
// Validate extracted strings match expected format
628+
expect(tripleQuoteStrings).toEqual([
629+
`"""FROM sample_data | LIMIT 3"""`,
630+
`"""
631+
FROM sample_data
632+
| WHERE message LIKE "Connected*"
633+
| SORT @timestamp DESC
634+
"""`,
635+
]);
636+
637+
// Ensure re-expansion gives the original input back
638+
const expanded = expandTripleQuoteStrings(collapsedTripleQuotesData, tripleQuoteStrings);
639+
expect(expanded).toBe(input);
640+
});
641+
642+
it('should be idempotent if run multiple times on collapsed data', () => {
643+
const firstCollapse = collapseTripleQuoteStrings(input);
644+
const secondCollapse = collapseTripleQuoteStrings(firstCollapse.collapsedTripleQuotesData);
645+
646+
expect(secondCollapse.tripleQuoteStrings).toEqual([]);
647+
expect(secondCollapse.collapsedTripleQuotesData).toBe(
648+
firstCollapse.collapsedTripleQuotesData
649+
);
650+
});
651+
});
559652
});

src/platform/plugins/shared/console/public/application/containers/editor/utils/requests_utils.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,36 @@ export const getRequestEndLineNumber = ({
132132
return endLineNumber;
133133
};
134134

135+
export const TRIPLE_QUOTE_STRINGS_MARKER = '"{tripleQuoteString}"';
136+
137+
/**
138+
* This function replaces all triple-quote strings with {@link TRIPLE_QUOTE_STRINGS_MARKER}
139+
*/
140+
export function collapseTripleQuoteStrings(data: string) {
141+
const splitData = data.split(`"""`);
142+
const tripleQuoteStrings = [];
143+
for (let i = 1; i < splitData.length - 1; i += 2) {
144+
tripleQuoteStrings.push('"""' + splitData[i] + '"""');
145+
splitData[i] = TRIPLE_QUOTE_STRINGS_MARKER;
146+
}
147+
return { collapsedTripleQuotesData: splitData.join(''), tripleQuoteStrings };
148+
}
149+
150+
/**
151+
* This function replaces all {@link TRIPLE_QUOTE_STRINGS_MARKER}s in the provided text with the corresponding provided triple-quote strings.
152+
*/
153+
export function expandTripleQuoteStrings(data: string, tripleQuoteStrings: string[]) {
154+
const splitData = data.split(TRIPLE_QUOTE_STRINGS_MARKER);
155+
const allData = [];
156+
for (let i = 0; i < splitData.length; i++) {
157+
allData.push(splitData[i]);
158+
if (i < tripleQuoteStrings.length) {
159+
allData.push(tripleQuoteStrings[i]);
160+
}
161+
}
162+
return allData.join('');
163+
}
164+
135165
/**
136166
* This function takes a string containing unformatted Console requests and
137167
* returns a text in which the requests are auto-indented.
@@ -179,7 +209,16 @@ export const getAutoIndentedRequests = (
179209
if (requestLines.length > 1) {
180210
const dataString = dataLines.join('\n');
181211
const dataJsons = splitDataIntoJsonObjects(dataString);
182-
formattedTextLines.push(...dataJsons.map(indentData));
212+
formattedTextLines.push(
213+
...dataJsons.map((data) => {
214+
// Since triple-quote strings are not a valid JSON syntax, we need to first collapse them before indenting the data
215+
const { collapsedTripleQuotesData, tripleQuoteStrings } =
216+
collapseTripleQuoteStrings(data);
217+
const indentedData = indentData(collapsedTripleQuotesData);
218+
// Return any collapsed triple-quote strings
219+
return expandTripleQuoteStrings(indentedData, tripleQuoteStrings);
220+
})
221+
);
183222
}
184223
}
185224

@@ -319,17 +358,28 @@ const splitDataIntoJsonObjects = (dataString: string): string[] => {
319358
let currentObject = '';
320359
// Tracks whether the current position is inside a string
321360
let insideString = false;
361+
// Tracks whether the current position is inside a triple-quote string
362+
let insideTripleQuoteString = false;
322363

364+
let i = 0;
323365
// Iterate through each character in the input string
324-
for (let i = 0; i < dataString.length; i++) {
366+
while (i < dataString.length) {
325367
const char = dataString[i];
326368
// Append the character to the current JSON object string
327369
currentObject += char;
328370

329-
// If the character is a double quote and it is not escaped, toggle the `insideString` state
330-
if (char === '"' && dataString[i - 1] !== '\\') {
371+
if (char === '"' && dataString.substring(i + 1, i + 3) === '""') {
372+
// If the character is a quote and the next two characters are also quotes,
373+
// toggle the `insideString` state
374+
insideTripleQuoteString = !insideTripleQuoteString;
375+
currentObject += '""';
376+
// Skip the next two quotes
377+
i += 2;
378+
} else if (!insideTripleQuoteString && char === '"' && dataString[i - 1] !== '\\') {
379+
// If the character is a quote, it is not escaped, and it's not inside a triple-quote string,
380+
// toggle the `insideString` state
331381
insideString = !insideString;
332-
} else if (!insideString) {
382+
} else if (!insideTripleQuoteString && !insideString) {
333383
// Only modify depth if not inside a string
334384

335385
if (char === '{') {
@@ -344,6 +394,7 @@ const splitDataIntoJsonObjects = (dataString: string): string[] => {
344394
currentObject = '';
345395
}
346396
}
397+
i++;
347398
}
348399

349400
// If there's remaining data in currentObject, add it as the last JSON object

0 commit comments

Comments
 (0)