Skip to content

Commit a16542a

Browse files
authored
[ES|QL] Improve PROMQL command params map parsing (elastic#250633)
## Summary Improves `PROMQL` command parsing: - Mostly improves params map index pattern parsing. Now the parser properly breaks down index patterns into their constituent parts (cluster, index, selector) and wraps them in a new "bare" list AST node type. - Added support for a new 'bare' list subtype—lists without any enclosing brackets. - The pretty printers (BasicPrettyPrinter and WrappingPrettyPrinter) now know how to format these correctly, without adding extra brackets or weird indentation. - Also added a helper `Builder.expression.list.bare()` for constructing these nodes programmatically. ### Checklist - [x] [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
1 parent fd7cba1 commit a16542a

10 files changed

Lines changed: 513 additions & 65 deletions

File tree

src/platform/packages/shared/kbn-esql-language/src/ast/builder/builder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,21 @@ export namespace Builder {
375375
name: '',
376376
};
377377
};
378+
379+
export const bare = (
380+
template: Omit<AstNodeTemplate<ESQLList>, 'name' | 'values'> &
381+
Partial<Pick<ESQLList, 'values'>> = {},
382+
fromParser?: Partial<AstNodeParserFields>
383+
): ESQLList => {
384+
return {
385+
values: [],
386+
...template,
387+
...Builder.parserFields(fromParser),
388+
type: 'list',
389+
subtype: 'bare',
390+
name: '',
391+
};
392+
};
378393
}
379394

380395
export namespace literal {

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/promql.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import { promqlFunctionDefinitions } from '../generated/promql_functions';
1818
import { buildFunctionDocumentation } from './documentation';
1919
import { withAutoSuggest } from './autocomplete/helpers';
20-
import { isIdentifier, isSource } from '../../../ast/is';
20+
import { isIdentifier, isList, isSource } from '../../../ast/is';
2121
import { SuggestionCategory } from '../../../shared/sorting';
2222
import { techPreviewLabel } from './shared';
2323

@@ -153,6 +153,21 @@ export function getIndexFromPromQLParams({
153153

154154
const { value } = indexEntry ?? {};
155155

156+
if (isList(value) && value.values.length > 0) {
157+
const listText = value.text?.trim();
158+
if (listText) {
159+
return listText;
160+
}
161+
162+
const names = value.values
163+
.map((item) => (isIdentifier(item) || isSource(item) ? item.name : ''))
164+
.filter(Boolean);
165+
166+
if (names.length > 0) {
167+
return names.join(',');
168+
}
169+
}
170+
156171
if ((isIdentifier(value) || isSource(value)) && !value.name.includes(EDITOR_MARKER)) {
157172
return value.name;
158173
}

src/platform/packages/shared/kbn-esql-language/src/commands/registry/promql/columns_after.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,25 @@ describe('PROMQL columnsAfter', () => {
6060

6161
expect(result).toEqual([]);
6262
});
63+
64+
it('passes multiple indices to fromFrom', async () => {
65+
const fromFrom = jest.fn().mockResolvedValue([]);
66+
67+
await columnsAfter(
68+
synth.cmd`PROMQL index=metrics,logs-tsdb rate(http_requests_total[5m])`,
69+
[],
70+
'',
71+
{
72+
fromFrom,
73+
fromJoin: () => Promise.resolve([]),
74+
fromEnrich: () => Promise.resolve([]),
75+
}
76+
);
77+
78+
expect(fromFrom).toHaveBeenCalledTimes(1);
79+
80+
const [cmd] = fromFrom.mock.calls[0];
81+
expect(String(cmd)).toContain('metrics');
82+
expect(String(cmd)).toContain('logs-tsdb');
83+
});
6384
});

src/platform/packages/shared/kbn-esql-language/src/commands/registry/promql/validate.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
import type {
1111
ESQLAst,
1212
ESQLAstAllCommands,
13+
ESQLAstExpression,
1314
ESQLAstPromqlCommand,
1415
ESQLLocation,
1516
ESQLMessage,
1617
} from '../../../types';
17-
import { isIdentifier } from '../../../ast/is';
18+
import { isIdentifier, isList, isSource } from '../../../ast/is';
1819
import type { ICommandContext } from '../types';
1920
import { getMessageFromId } from '../../definitions/utils';
2021
import { sourceExists } from '../../definitions/utils/sources';
@@ -219,7 +220,7 @@ function collectPromqlParamValues(
219220
continue;
220221
}
221222

222-
const rawValue = entry.value.text?.trim() ?? '';
223+
const rawValue = getPromqlParamValueText(entry.value);
223224
const value = looksLikePromqlParamAssignment(rawValue) ? '' : rawValue;
224225

225226
values.set(key, {
@@ -245,6 +246,24 @@ function collectPromqlParamValues(
245246
return values;
246247
}
247248

249+
function getPromqlParamValueText(value: ESQLAstExpression | undefined): string {
250+
if (!value) return '';
251+
252+
const text = value.text?.trim();
253+
if (text) return text;
254+
255+
if (isList(value)) {
256+
const parts = value.values
257+
.map((item) => item.text?.trim() || (isIdentifier(item) || isSource(item) ? item.name : ''))
258+
.filter(Boolean);
259+
260+
return parts.join(',');
261+
}
262+
263+
if (isIdentifier(value) || isSource(value)) return value.name;
264+
265+
return '';
266+
}
248267
/*
249268
* Extracts the PromQL query text from the AST.
250269
*
@@ -314,7 +333,7 @@ function getPromqlQueryTail(command: ESQLAstPromqlCommand): string {
314333
continue;
315334
}
316335

317-
const rawValue = entry.value.text ?? '';
336+
const rawValue = getPromqlParamValueText(entry.value);
318337
const keyPrefix = `${key}=`;
319338
const valuePrefix = `${keyPrefix}${rawValue}`;
320339
const lowerRest = rest.toLowerCase();

0 commit comments

Comments
 (0)