mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki Query Builder: binary expression and numeric literal bugs (#74950)
* Following similar changes made to prometheus in #47198, reverse the order of binary operator parameter array, and fix bugs introduced by importing prometheus lezer constants into loki parser * add unit test asserting buildVisualQueryFromString does not properly parse queries with nested binary operations * fix onOrIgnoring parsing logic which was always stripping out the value of the matcher when a boolean was found
This commit is contained in:
parent
06d89e1929
commit
ac98197132
@ -82,12 +82,12 @@ export const binaryScalarOperations: QueryBuilderOperationDef[] = binaryScalarDe
|
||||
const params: QueryBuilderOperationParamDef[] = [{ name: 'Value', type: 'number' }];
|
||||
const defaultParams: any[] = [2];
|
||||
if (opDef.comparison) {
|
||||
params.unshift({
|
||||
params.push({
|
||||
name: 'Bool',
|
||||
type: 'boolean',
|
||||
description: 'If checked comparison will return 0 or 1 for the value rather than filtering.',
|
||||
});
|
||||
defaultParams.unshift(false);
|
||||
defaultParams.push(false);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -107,8 +107,7 @@ function getSimpleBinaryRenderer(operator: string) {
|
||||
let param = model.params[0];
|
||||
let bool = '';
|
||||
if (model.params.length === 2) {
|
||||
param = model.params[1];
|
||||
bool = model.params[0] ? ' bool' : '';
|
||||
bool = model.params[1] ? ' bool' : '';
|
||||
}
|
||||
|
||||
return `${innerExpr} ${operator}${bool} ${param}`;
|
||||
|
@ -11,6 +11,116 @@ describe('buildVisualQueryFromString', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('parses simple binary comparison', () => {
|
||||
expect(buildVisualQueryFromString('count_over_time({app="aggregator"} [$__auto]) == 11')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
id: LokiOperationId.CountOverTime,
|
||||
params: ['$__auto'],
|
||||
},
|
||||
{
|
||||
id: LokiOperationId.EqualTo,
|
||||
// defined in getSimpleBinaryRenderer, the first argument is the bool value, and the second is the comparison operator
|
||||
params: [11, false],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
// This still fails because loki doesn't properly parse the bool operator
|
||||
it('parses simple query with label-values with boolean operator', () => {
|
||||
expect(buildVisualQueryFromString('count_over_time({app="aggregator"} [$__auto]) == bool 12')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
id: LokiOperationId.CountOverTime,
|
||||
params: ['$__auto'],
|
||||
},
|
||||
{
|
||||
id: LokiOperationId.EqualTo,
|
||||
// defined in getSimpleBinaryRenderer, the first argument is the bool value, and the second is the comparison operator
|
||||
params: [12, true],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('parses binary operation with query', () => {
|
||||
expect(
|
||||
// There is no capability for "bool" in the query builder for (nested) binary operation with query as of now, it will always be stripped out
|
||||
buildVisualQueryFromString(
|
||||
'max by(stream) (count_over_time({app="aggregator"}[1m])) > bool ignoring(stream) avg(count_over_time({app="aggregator"}[1m]))'
|
||||
)
|
||||
).toEqual({
|
||||
query: {
|
||||
binaryQueries: [
|
||||
{
|
||||
// nested binary operation
|
||||
operator: '>',
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
id: 'count_over_time',
|
||||
params: ['1m'],
|
||||
},
|
||||
{
|
||||
id: 'avg',
|
||||
params: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
vectorMatches: 'stream',
|
||||
vectorMatchesType: 'ignoring',
|
||||
},
|
||||
],
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
operations: [
|
||||
{
|
||||
id: 'count_over_time',
|
||||
params: ['1m'],
|
||||
},
|
||||
{
|
||||
id: '__max_by',
|
||||
params: ['stream'],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('parses simple query with label-values', () => {
|
||||
expect(buildVisualQueryFromString('{app="frontend"}')).toEqual(
|
||||
noErrors({
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { SyntaxNode } from '@lezer/common';
|
||||
import { BinModifiers, OnOrIgnoring } from '@prometheus-io/lezer-promql';
|
||||
|
||||
import {
|
||||
And,
|
||||
@ -50,6 +49,8 @@ import {
|
||||
VectorAggregationExpr,
|
||||
VectorOp,
|
||||
Without,
|
||||
BinOpModifier,
|
||||
OnOrIgnoringModifier,
|
||||
} from '@grafana/lezer-logql';
|
||||
|
||||
import {
|
||||
@ -565,7 +566,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
const visQuery = context.query;
|
||||
const left = node.firstChild!;
|
||||
const op = getString(expr, left.nextSibling);
|
||||
const binModifier = getBinaryModifier(expr, node.getChild(BinModifiers));
|
||||
const binModifier = getBinaryModifier(expr, node.getChild(BinOpModifier));
|
||||
|
||||
const right = node.lastChild!;
|
||||
|
||||
@ -591,7 +592,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
// Due to the way binary ops are parsed we can get a binary operation on the right that starts with a number which
|
||||
// is a factor for a current binary operation. So we have to add it as an operation now.
|
||||
const leftMostChild = getLeftMostChild(right);
|
||||
if (leftMostChild?.name === 'Number') {
|
||||
if (leftMostChild?.type.id === NumberLezer) {
|
||||
visQuery.operations.push(makeBinOp(opDef, expr, leftMostChild, !!binModifier?.isBool));
|
||||
}
|
||||
|
||||
@ -624,15 +625,17 @@ function getBinaryModifier(
|
||||
node: SyntaxNode | null
|
||||
):
|
||||
| { isBool: true; isMatcher: false }
|
||||
| { isBool: false; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
||||
| { isBool: boolean; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
||||
| undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
if (node.getChild(Bool)) {
|
||||
const matcher = node.getChild(OnOrIgnoringModifier);
|
||||
const boolMatcher = node.getChild(Bool);
|
||||
|
||||
if (!matcher && boolMatcher) {
|
||||
return { isBool: true, isMatcher: false };
|
||||
} else {
|
||||
const matcher = node.getChild(OnOrIgnoring);
|
||||
if (!matcher) {
|
||||
// Not sure what this could be, maybe should be an error.
|
||||
return undefined;
|
||||
@ -640,7 +643,7 @@ function getBinaryModifier(
|
||||
const labels = getString(expr, matcher.getChild(GroupingLabels)?.getChild(GroupingLabelList));
|
||||
return {
|
||||
isMatcher: true,
|
||||
isBool: false,
|
||||
isBool: !!boolMatcher,
|
||||
matches: labels,
|
||||
matchType: matcher.getChild(On) ? 'on' : 'ignoring',
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { buildVisualQueryFromString } from './parsing';
|
||||
import { PromVisualQuery } from './types';
|
||||
import { PromOperationId, PromVisualQuery } from './types';
|
||||
|
||||
describe('buildVisualQueryFromString', () => {
|
||||
it('creates no errors for empty query', () => {
|
||||
@ -11,6 +11,50 @@ describe('buildVisualQueryFromString', () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
it('parses simple binary comparison', () => {
|
||||
expect(buildVisualQueryFromString('{app="aggregator"} == 11')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
metric: '',
|
||||
operations: [
|
||||
{
|
||||
id: PromOperationId.EqualTo,
|
||||
params: [11, false],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
|
||||
// This still fails because loki doesn't properly parse the bool operator
|
||||
it('parses simple query with with boolean operator', () => {
|
||||
expect(buildVisualQueryFromString('{app="aggregator"} == bool 12')).toEqual({
|
||||
query: {
|
||||
labels: [
|
||||
{
|
||||
label: 'app',
|
||||
op: '=',
|
||||
value: 'aggregator',
|
||||
},
|
||||
],
|
||||
metric: '',
|
||||
operations: [
|
||||
{
|
||||
id: PromOperationId.EqualTo,
|
||||
params: [12, true],
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: [],
|
||||
});
|
||||
});
|
||||
it('parses simple query', () => {
|
||||
expect(buildVisualQueryFromString('counters_logins{app="frontend"}')).toEqual(
|
||||
noErrors({
|
||||
|
Loading…
Reference in New Issue
Block a user