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 <sven.grossmann@grafana.com>

* Add comment

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
Matias Chomicki
2025-04-15 20:20:40 +01:00
committed by GitHub
co-authored by Sven Grossmann
parent b3b92e2471
commit b2847f3b4d
5 changed files with 38 additions and 5 deletions
@@ -213,8 +213,8 @@ export const LokiQueryEditor = memo<LokiQueryEditorProps>((props) => {
onChange={onChange}
onRunQuery={onRunQuery}
app={app}
maxLines={datasource.maxLines}
queryStats={queryStats}
datasource={props.datasource}
/>
</EditorRows>
</>
@@ -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<LokiQuery> = {}, onChange = jest.fn(), propOverrides: Partial<Props> = {}) {
const datasource = createLokiDatasource();
datasource.maxLines = 20;
const props = {
query: {
refId: 'A',
@@ -283,7 +287,7 @@ function setup(queryOverrides: Partial<LokiQuery> = {}, onChange = jest.fn(), pr
},
onRunQuery: jest.fn(),
onChange,
maxLines: 20,
datasource,
queryStats: { streams: 0, chunks: 0, bytes: 0, entries: 0 },
...propOverrides,
};
@@ -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<Props>(
({ 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<Props>(
}, [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;
@@ -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({
@@ -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) {