From b2847f3b4da6bfdc323bfc2d9e2a638181e19348 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Tue, 15 Apr 2025 21:20:40 +0200 Subject: [PATCH] Loki Query Editor: Interpolate query before evaluating type and add variables support to ranged aggregation (#104035) * LokiQueryBuilderOptions: interpolate query before evaluating * Query builder: parse variables in vector aggregation * Fix imports * Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx Co-authored-by: Sven Grossmann * Add comment --------- Co-authored-by: Sven Grossmann --- .../loki/components/LokiQueryEditor.tsx | 2 +- .../LokiQueryBuilderOptions.test.tsx | 6 +++++- .../components/LokiQueryBuilderOptions.tsx | 10 ++++++--- .../loki/querybuilder/parsing.test.ts | 21 +++++++++++++++++++ .../datasource/loki/querybuilder/parsing.ts | 4 ++++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index a6da1b06ff8..677ec742b69 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -213,8 +213,8 @@ export const LokiQueryEditor = memo((props) => { onChange={onChange} onRunQuery={onRunQuery} app={app} - maxLines={datasource.maxLines} queryStats={queryStats} + datasource={props.datasource} /> diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx index 6e48e201798..51d805667bf 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event'; import { CoreApp, LogSortOrderChangeEvent, LogsSortOrder, store } from '@grafana/data'; import { config, getAppEvents } from '@grafana/runtime'; +import { createLokiDatasource } from '../../__mocks__/datasource'; import { LokiQuery, LokiQueryDirection, LokiQueryType } from '../../types'; import { LokiQueryBuilderOptions, Props } from './LokiQueryBuilderOptions'; @@ -275,6 +276,9 @@ describe('LokiQueryBuilderOptions', () => { }); function setup(queryOverrides: Partial = {}, onChange = jest.fn(), propOverrides: Partial = {}) { + const datasource = createLokiDatasource(); + datasource.maxLines = 20; + const props = { query: { refId: 'A', @@ -283,7 +287,7 @@ function setup(queryOverrides: Partial = {}, onChange = jest.fn(), pr }, onRunQuery: jest.fn(), onChange, - maxLines: 20, + datasource, queryStats: { streams: 0, chunks: 0, bytes: 0, entries: 0 }, ...propOverrides, }; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx index 69da069fb91..f692bf9496a 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -20,6 +20,8 @@ import { queryDirections, queryTypeOptions, } from '../../components/LokiOptionFields'; +import { placeHolderScopedVars } from '../../components/monaco-query-field/monaco-completion-provider/validation'; +import { LokiDatasource } from '../../datasource'; import { getLokiQueryType, isLogsQuery } from '../../queryUtils'; import { LokiQuery, LokiQueryDirection, LokiQueryType, QueryStats } from '../../types'; @@ -27,14 +29,15 @@ export interface Props { query: LokiQuery; onChange: (update: LokiQuery) => void; onRunQuery: () => void; - maxLines: number; app?: CoreApp; queryStats: QueryStats | null; + datasource: LokiDatasource; } export const LokiQueryBuilderOptions = React.memo( - ({ app, query, onChange, onRunQuery, maxLines, queryStats }) => { + ({ app, query, onChange, onRunQuery, queryStats, datasource }) => { const [splitDurationValid, setSplitDurationValid] = useState(true); + const maxLines = datasource.maxLines; useEffect(() => { if (app !== CoreApp.Explore && app !== CoreApp.Dashboard && app !== CoreApp.PanelEditor) { @@ -119,7 +122,8 @@ export const LokiQueryBuilderOptions = React.memo( }, [app, onQueryDirectionChange, query.direction]); let queryType = getLokiQueryType(query); - const isLogQuery = isLogsQuery(query.expr); + const interpolatedQueries = datasource.interpolateVariablesInQueries([query], placeHolderScopedVars); + const isLogQuery = isLogsQuery(interpolatedQueries[0]?.expr ?? ''); const filteredQueryTypeOptions = isLogQuery ? queryTypeOptions.filter((o) => o.value !== LokiQueryType.Instant) : queryTypeOptions; diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts index dd4fe45a552..9427616522e 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.test.ts @@ -634,6 +634,27 @@ describe('buildVisualQueryFromString', () => { ); }); + it('parses metrics query with vector aggregation with variable', () => { + expect( + buildVisualQueryFromString('topk($variable, sum by(unit) (count_over_time({app="frontend"}[$__auto])))') + ).toEqual( + noErrors({ + labels: [ + { + op: '=', + value: 'frontend', + label: 'app', + }, + ], + operations: [ + { id: LokiOperationId.CountOverTime, params: ['$__auto'] }, + { id: LokiOperationId.SumBy, params: ['unit'] }, + { id: LokiOperationId.TopK, params: ['$variable'] }, + ], + }) + ); + }); + it('parses template variables in strings', () => { expect(buildVisualQueryFromString('{instance="$label_variable"}')).toEqual( noErrors({ diff --git a/public/app/plugins/datasource/loki/querybuilder/parsing.ts b/public/app/plugins/datasource/loki/querybuilder/parsing.ts index 200746b154f..f86069e53d6 100644 --- a/public/app/plugins/datasource/loki/querybuilder/parsing.ts +++ b/public/app/plugins/datasource/loki/querybuilder/parsing.ts @@ -526,9 +526,13 @@ function handleVectorAggregation(expr: string, node: SyntaxNode, context: Contex const params = []; const numberNode = node.getChild(NumberLezer); + const errorNode = node.getChild(ErrorId)?.getChild(Identifier); if (numberNode) { params.push(Number(getString(expr, numberNode))); + } else if (errorNode) { + // Variables get parsed as errors, so the value us an identifier within an error node. + params.push(getString(expr, errorNode)); } if (grouping) {