From 24cf70dd291e7a6f42f64e78539b4e4744781cd1 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:11:15 +0100 Subject: [PATCH] Loki: Add parsing for vector aggregations with grouping (#47257) * Parse aggregations with grouping * Move getByType to shared parsing utils --- .../loki/querybuilder/parsing.test.ts | 38 +++++++++++++++++++ .../datasource/loki/querybuilder/parsing.ts | 22 +++++++++-- .../prometheus/querybuilder/parsing.ts | 33 +++++----------- .../querybuilder/shared/parsingUtils.ts | 23 +++++++++++ 4 files changed, 89 insertions(+), 27 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts index 306dfc50eb3..b123eb2fc7d 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts @@ -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({ diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.ts index f6e58d73799..f8b6cd9041b 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.ts @@ -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); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts index c8a7d145ace..0db720b4b0f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts @@ -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; -} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils.ts b/public/app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils.ts index 8fa3db87621..9895860e27e 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils.ts @@ -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) {