From 68eb18c758db8f308ad98a20dd3e360c60d65c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 10 Feb 2022 11:48:48 +0100 Subject: [PATCH] Prometheus: Support with aggregations without labels (#45160) --- .../querybuilder/PromQueryModeller.test.ts | 10 +++++ .../prometheus/querybuilder/aggregations.ts | 37 +++++++++++++++++-- .../prometheus/querybuilder/parsing.test.ts | 25 +++++++++++++ .../prometheus/querybuilder/parsing.ts | 7 +++- 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts index 649710338fb..170a1d7e21f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/PromQueryModeller.test.ts @@ -80,6 +80,16 @@ describe('PromQueryModeller', () => { ).toBe('avg(sum by(server, job) (metric))'); }); + it('Can use aggregation without label', () => { + expect( + modeller.renderQuery({ + metric: 'metric', + labels: [], + operations: [{ id: '__sum_without', params: ['server', 'job'] }], + }) + ).toBe('sum without(server, job) (metric)'); + }); + it('Can render aggregations with parameters', () => { expect( modeller.renderQuery({ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/aggregations.ts b/public/app/plugins/datasource/prometheus/querybuilder/aggregations.ts index ce34bee9faf..2ffe3c3c9aa 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/aggregations.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/aggregations.ts @@ -67,7 +67,28 @@ function createAggregationOperation(name: string): QueryBuilderOperationDef[] { renderer: getAggregationByRenderer(name), addOperationHandler: defaultAddOperationHandler, paramChangedHandler: getLastLabelRemovedHandler(name), - explainHandler: getAggregationExplainer(name), + explainHandler: getAggregationExplainer(name, 'by'), + hideFromList: true, + }, + { + id: `__${name}_without`, + name: `${getPromAndLokiOperationDisplayName(name)} without`, + params: [ + { + name: 'Label', + type: 'string', + restParam: true, + optional: true, + editor: LabelParamEditor, + }, + ], + defaultParams: [''], + alternativesKey: 'aggregations by', + category: PromVisualQueryOperationCategory.Aggregations, + renderer: getAggregationWithoutRenderer(name), + addOperationHandler: defaultAddOperationHandler, + paramChangedHandler: getLastLabelRemovedHandler(name), + explainHandler: getAggregationExplainer(name, 'without'), hideFromList: true, }, ]; @@ -94,14 +115,24 @@ function getAggregationByRenderer(aggregation: string) { }; } +function getAggregationWithoutRenderer(aggregation: string) { + return function aggregationRenderer(model: QueryBuilderOperation, def: QueryBuilderOperationDef, innerExpr: string) { + return `${aggregation} without(${model.params.join(', ')}) (${innerExpr})`; + }; +} + /** * Very simple poc implementation, needs to be modified to support all aggregation operators */ -function getAggregationExplainer(aggregationName: string) { +function getAggregationExplainer(aggregationName: string, mode: 'by' | 'without') { return function aggregationExplainer(model: QueryBuilderOperation) { const labels = model.params.map((label) => `\`${label}\``).join(' and '); const labelWord = pluralize('label', model.params.length); - return `Calculates ${aggregationName} over dimensions while preserving ${labelWord} ${labels}.`; + if (mode === 'by') { + return `Calculates ${aggregationName} over dimensions while preserving ${labelWord} ${labels}.`; + } else { + return `Calculates ${aggregationName} over the dimensions ${labels}. All other labels are preserved.`; + } }; } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts index e8217266943..7d546246deb 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.test.ts @@ -93,6 +93,31 @@ describe('buildVisualQueryFromString', () => { ); }); + it('parses query with aggregation without labels', () => { + const visQuery = { + metric: 'metric_name', + labels: [ + { + label: 'instance', + op: '=', + value: 'internal:3000', + }, + ], + operations: [ + { + id: '__sum_without', + params: ['app', 'version'], + }, + ], + }; + expect(buildVisualQueryFromString('sum(metric_name{instance="internal:3000"}) without (app, version)')).toEqual( + noErrors(visQuery) + ); + expect(buildVisualQueryFromString('sum without (app, version)(metric_name{instance="internal:3000"})')).toEqual( + noErrors(visQuery) + ); + }); + it('parses aggregation with params', () => { expect(buildVisualQueryFromString('topk(5, http_requests_total)')).toEqual( noErrors({ diff --git a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts index 88a4d44083f..f1650e1c4b5 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/parsing.ts @@ -233,12 +233,17 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) { const modifier = node.getChild('AggregateModifier'); const labels = []; - // TODO: support also Without modifier (but we don't support it in visual query yet) if (modifier) { const byModifier = modifier.getChild(`By`); if (byModifier && funcName) { funcName = `__${funcName}_by`; } + + const withoutModifier = modifier.getChild(`Without`); + if (withoutModifier) { + funcName = `__${funcName}_without`; + } + labels.push(...getAllByType(expr, modifier, 'GroupingLabel')); }