Loki: Fix filters not being added with multiple expressions and parsers (#75152)

* determine last positions per expr

* fix lint
This commit is contained in:
Sven Grossmann 2023-09-20 16:54:24 +02:00 committed by GitHub
parent 15e54df9f2
commit 480aa1ccca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 15 deletions

View File

@ -59,6 +59,7 @@ describe('addLabelToQuery()', () => {
${'{foo="bar"} | logfmt'} | ${'query with parser with escaped value and regex operator'} | ${'bar'} | ${'~='} | ${'\\"baz\\"'} | ${'{foo="bar"} | logfmt | bar~=`"baz"`'} ${'{foo="bar"} | logfmt'} | ${'query with parser with escaped value and regex operator'} | ${'bar'} | ${'~='} | ${'\\"baz\\"'} | ${'{foo="bar"} | logfmt | bar~=`"baz"`'}
${'{foo="bar"} | logfmt'} | ${'query with parser, > operator and number value'} | ${'bar'} | ${'>'} | ${'5'} | ${'{foo="bar"} | logfmt | bar>5'} ${'{foo="bar"} | logfmt'} | ${'query with parser, > operator and number value'} | ${'bar'} | ${'>'} | ${'5'} | ${'{foo="bar"} | logfmt | bar>5'}
${'{foo="bar"} | logfmt'} | ${'query with parser, < operator and non-number value'} | ${'bar'} | ${'<'} | ${'5KiB'} | ${'{foo="bar"} | logfmt | bar<`5KiB`'} ${'{foo="bar"} | logfmt'} | ${'query with parser, < operator and non-number value'} | ${'bar'} | ${'<'} | ${'5KiB'} | ${'{foo="bar"} | logfmt | bar<`5KiB`'}
${'sum(rate({x="y"} | logfmt [5m])) + sum(rate({x="z"} | logfmt [5m]))'} | ${'metric query with non empty selectors and parsers'} | ${'bar'} | ${'='} | ${'baz'} | ${'sum(rate({x="y"} | logfmt | bar=`baz` [5m])) + sum(rate({x="z"} | logfmt | bar=`baz` [5m]))'}
`( `(
'should add label to query: $query, description: $description', 'should add label to query: $query, description: $description',
({ query, description, label, operator, value, expectedResult }) => { ({ query, description, label, operator, value, expectedResult }) => {

View File

@ -17,11 +17,13 @@ import {
UnwrapExpr, UnwrapExpr,
String, String,
PipelineStage, PipelineStage,
Expr,
} from '@grafana/lezer-logql'; } from '@grafana/lezer-logql';
import { QueryBuilderLabelFilter } from '../prometheus/querybuilder/shared/types'; import { QueryBuilderLabelFilter } from '../prometheus/querybuilder/shared/types';
import { unescapeLabelValue } from './languageUtils'; import { unescapeLabelValue } from './languageUtils';
import { getNodePositionsFromQuery } from './queryUtils';
import { lokiQueryModeller as modeller } from './querybuilder/LokiQueryModeller'; import { lokiQueryModeller as modeller } from './querybuilder/LokiQueryModeller';
import { buildVisualQueryFromString, handleQuotes } from './querybuilder/parsing'; import { buildVisualQueryFromString, handleQuotes } from './querybuilder/parsing';
@ -184,8 +186,20 @@ export function addLabelToQuery(
const positionToAdd = findLastPosition([...streamSelectorPositions, ...labelFilterPositions, ...parserPositions]); const positionToAdd = findLastPosition([...streamSelectorPositions, ...labelFilterPositions, ...parserPositions]);
return addFilterAsLabelFilter(query, [positionToAdd], filter); return addFilterAsLabelFilter(query, [positionToAdd], filter);
} else if (everyStreamSelectorHasMatcher && (labelFilterPositions.length || parserPositions.length)) { } else if (everyStreamSelectorHasMatcher && (labelFilterPositions.length || parserPositions.length)) {
const positionToAdd = findLastPosition([...labelFilterPositions, ...parserPositions]); // in case we are not adding the label to stream selectors we need to find the last position to add in each expression
return addFilterAsLabelFilter(query, [positionToAdd], filter); const subExpressions = findLeaves(getNodePositionsFromQuery(query, [Expr]));
const parserFilterPositions = [...parserPositions, ...labelFilterPositions];
// find last position for each subexpression
const lastPositionsPerExpression = subExpressions.map((subExpression) => {
return findLastPosition(
parserFilterPositions.filter((p) => {
return subExpression.contains(p);
})
);
});
return addFilterAsLabelFilter(query, lastPositionsPerExpression, filter);
} else { } else {
return addFilterToStreamSelector(query, streamSelectorPositions, filter); return addFilterToStreamSelector(query, streamSelectorPositions, filter);
} }
@ -271,9 +285,9 @@ export function getStreamSelectorPositions(query: string): NodePosition[] {
const tree = parser.parse(query); const tree = parser.parse(query);
const positions: NodePosition[] = []; const positions: NodePosition[] = [];
tree.iterate({ tree.iterate({
enter: ({ type, from, to }): false | void => { enter: ({ type, node }): false | void => {
if (type.id === Selector) { if (type.id === Selector) {
positions.push(new NodePosition(from, to, type)); positions.push(NodePosition.fromNode(node));
return false; return false;
} }
}, },
@ -287,7 +301,7 @@ function getMatcherInStreamPositions(query: string): NodePosition[] {
tree.iterate({ tree.iterate({
enter: ({ node }): false | void => { enter: ({ node }): false | void => {
if (node.type.id === Selector) { if (node.type.id === Selector) {
positions.push(...getAllPositionsInNodeByType(query, node, Matcher)); positions.push(...getAllPositionsInNodeByType(node, Matcher));
} }
}, },
}); });
@ -302,9 +316,9 @@ export function getParserPositions(query: string): NodePosition[] {
const tree = parser.parse(query); const tree = parser.parse(query);
const positions: NodePosition[] = []; const positions: NodePosition[] = [];
tree.iterate({ tree.iterate({
enter: ({ type, from, to }): false | void => { enter: ({ type, node }): false | void => {
if (type.id === LabelParser || type.id === JsonExpressionParser) { if (type.id === LabelParser || type.id === JsonExpressionParser) {
positions.push(new NodePosition(from, to, type)); positions.push(NodePosition.fromNode(node));
return false; return false;
} }
}, },
@ -320,9 +334,9 @@ export function getLabelFilterPositions(query: string): NodePosition[] {
const tree = parser.parse(query); const tree = parser.parse(query);
const positions: NodePosition[] = []; const positions: NodePosition[] = [];
tree.iterate({ tree.iterate({
enter: ({ type, from, to }): false | void => { enter: ({ type, node }): false | void => {
if (type.id === LabelFilter) { if (type.id === LabelFilter) {
positions.push(new NodePosition(from, to, type)); positions.push(NodePosition.fromNode(node));
return false; return false;
} }
}, },
@ -338,9 +352,9 @@ function getLineFiltersPositions(query: string): NodePosition[] {
const tree = parser.parse(query); const tree = parser.parse(query);
const positions: NodePosition[] = []; const positions: NodePosition[] = [];
tree.iterate({ tree.iterate({
enter: ({ type, from, to }): false | void => { enter: ({ type, node }): false | void => {
if (type.id === LineFilters) { if (type.id === LineFilters) {
positions.push(new NodePosition(from, to, type)); positions.push(NodePosition.fromNode(node));
return false; return false;
} }
}, },
@ -356,9 +370,9 @@ function getLogQueryPositions(query: string): NodePosition[] {
const tree = parser.parse(query); const tree = parser.parse(query);
const positions: NodePosition[] = []; const positions: NodePosition[] = [];
tree.iterate({ tree.iterate({
enter: ({ type, from, to, node }): false | void => { enter: ({ type, node }): false | void => {
if (type.id === LogExpr) { if (type.id === LogExpr) {
positions.push(new NodePosition(from, to, type)); positions.push(NodePosition.fromNode(node));
return false; return false;
} }
@ -565,7 +579,7 @@ export function findLastPosition(positions: NodePosition[]): NodePosition {
return positions.reduce((prev, current) => (prev.to > current.to ? prev : current)); return positions.reduce((prev, current) => (prev.to > current.to ? prev : current));
} }
function getAllPositionsInNodeByType(query: string, node: SyntaxNode, type: number): NodePosition[] { function getAllPositionsInNodeByType(node: SyntaxNode, type: number): NodePosition[] {
if (node.type.id === type) { if (node.type.id === type) {
return [NodePosition.fromNode(node)]; return [NodePosition.fromNode(node)];
} }
@ -574,9 +588,19 @@ function getAllPositionsInNodeByType(query: string, node: SyntaxNode, type: numb
let pos = 0; let pos = 0;
let child = node.childAfter(pos); let child = node.childAfter(pos);
while (child) { while (child) {
positions.push(...getAllPositionsInNodeByType(query, child, type)); positions.push(...getAllPositionsInNodeByType(child, type));
pos = child.to; pos = child.to;
child = node.childAfter(pos); child = node.childAfter(pos);
} }
return positions; return positions;
} }
/**
* Gets all leaves of the nodes given. Leaves are nodes that don't contain any other nodes.
*
* @param {NodePosition[]} nodes
* @return
*/
function findLeaves(nodes: NodePosition[]): NodePosition[] {
return nodes.filter((node) => nodes.every((n) => node.contains(n) === false || node === n));
}