Loki: Add parsing for vector aggregations with grouping (#47257)

* Parse aggregations with grouping

* Move getByType to shared parsing utils
This commit is contained in:
Ivana Huckova 2022-04-04 17:11:15 +01:00 committed by GitHub
parent 78bdcede70
commit 24cf70dd29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 27 deletions

View File

@ -179,6 +179,44 @@ describe('buildVisualQueryFromString', () => {
);
});
it('parses metrics query with function and aggregation with grouping', () => {
expect(buildVisualQueryFromString('sum by (job,name) (rate({app="frontend"} | json [5m]))')).toEqual(
noErrors({
labels: [
{
op: '=',
value: 'frontend',
label: 'app',
},
],
operations: [
{ id: 'json', params: [] },
{ id: 'rate', params: ['5m'] },
{ id: '__sum_by', params: ['job', 'name'] },
],
})
);
});
it('parses metrics query with function and aggregation with grouping at the end', () => {
expect(buildVisualQueryFromString('sum(rate({app="frontend"} | json [5m])) without(job,name)')).toEqual(
noErrors({
labels: [
{
op: '=',
value: 'frontend',
label: 'app',
},
],
operations: [
{ id: 'json', params: [] },
{ id: 'rate', params: ['5m'] },
{ id: '__sum_without', params: ['job', 'name'] },
],
})
);
});
it('parses metrics query with function and aggregation and filters', () => {
expect(buildVisualQueryFromString('sum(rate({app="frontend"} |~ `abc` | json | bar="baz" [5m]))')).toEqual(
noErrors({

View File

@ -2,6 +2,7 @@ import { parser } from '@grafana/lezer-logql';
import { SyntaxNode } from '@lezer/common';
import {
ErrorName,
getAllByType,
getLeftMostChild,
getString,
makeBinOp,
@ -217,7 +218,6 @@ function getLabelFilter(expr: string, node: SyntaxNode): QueryBuilderOperation {
}
function getLineFormat(expr: string, node: SyntaxNode): QueryBuilderOperation {
// Not implemented in visual query builder yet
const id = 'line_format';
const string = handleQuotes(getString(expr, node.getChild('String')));
@ -228,7 +228,6 @@ function getLineFormat(expr: string, node: SyntaxNode): QueryBuilderOperation {
}
function getLabelFormat(expr: string, node: SyntaxNode): QueryBuilderOperation {
// Not implemented in visual query builder yet
const id = 'label_format';
const identifier = node.getChild('Identifier');
const op = identifier!.nextSibling;
@ -270,8 +269,25 @@ function handleVectorAggregation(expr: string, node: SyntaxNode, context: Contex
const nameNode = node.getChild('VectorOp');
let funcName = getString(expr, nameNode);
const grouping = node.getChild('Grouping');
const labels: string[] = [];
if (grouping) {
const byModifier = grouping.getChild(`By`);
if (byModifier && funcName) {
funcName = `__${funcName}_by`;
}
const withoutModifier = grouping.getChild(`Without`);
if (withoutModifier) {
funcName = `__${funcName}_without`;
}
labels.push(...getAllByType(expr, grouping, 'Identifier'));
}
const metricExpr = node.getChild('MetricExpr');
const op: QueryBuilderOperation = { id: funcName, params: [] };
const op: QueryBuilderOperation = { id: funcName, params: labels };
if (metricExpr) {
handleExpression(expr, metricExpr, context);

View File

@ -3,7 +3,15 @@ import { SyntaxNode } from '@lezer/common';
import { QueryBuilderLabelFilter, QueryBuilderOperation } from './shared/types';
import { PromVisualQuery, PromVisualQueryBinary } from './types';
import { binaryScalarDefs } from './binaryScalarOperations';
import { ErrorName, getLeftMostChild, getString, makeBinOp, makeError, replaceVariables } from './shared/parsingUtils';
import {
ErrorName,
getAllByType,
getLeftMostChild,
getString,
makeBinOp,
makeError,
replaceVariables,
} from './shared/parsingUtils';
/**
* Parses a PromQL query into a visual query model.
@ -365,26 +373,3 @@ function getBinaryModifier(
};
}
}
/**
* Get all nodes with type in the tree. This traverses the tree so it is safe only when you know there shouldn't be
* too much nesting but you just want to skip some of the wrappers. For example getting function args this way would
* not be safe is it would also find arguments of nested functions.
* @param expr
* @param cur
* @param type
*/
function getAllByType(expr: string, cur: SyntaxNode, type: string): string[] {
if (cur.name === type) {
return [getString(expr, cur)];
}
const values: string[] = [];
let pos = 0;
let child = cur.childAfter(pos);
while (child) {
values.push(...getAllByType(expr, child, type));
pos = child.to;
child = cur.childAfter(pos);
}
return values;
}

View File

@ -106,6 +106,29 @@ export function makeBinOp(
};
}
/**
* Get all nodes with type in the tree. This traverses the tree so it is safe only when you know there shouldn't be
* too much nesting but you just want to skip some of the wrappers. For example getting function args this way would
* not be safe is it would also find arguments of nested functions.
* @param expr
* @param cur
* @param type
*/
export function getAllByType(expr: string, cur: SyntaxNode, type: string): string[] {
if (cur.name === type) {
return [getString(expr, cur)];
}
const values: string[] = [];
let pos = 0;
let child = cur.childAfter(pos);
while (child) {
values.push(...getAllByType(expr, child, type));
pos = child.to;
child = cur.childAfter(pos);
}
return values;
}
// Debugging function for convenience. Gives you nice output similar to linux tree util.
// @ts-ignore
export function log(expr: string, cur?: SyntaxNode) {