Browse Source

Clean up the Lezer grammar output tree (#11333)

Signed-off-by: Marijn Haverbeke <marijn@haverbeke.nl>
pull/11445/head
Marijn Haverbeke 2 years ago committed by GitHub
parent
commit
8dbb2eaf0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 63
      web/ui/module/codemirror-promql/src/complete/hybrid.ts
  2. 2
      web/ui/module/codemirror-promql/src/parser/index.ts
  3. 56
      web/ui/module/codemirror-promql/src/parser/parser.ts
  4. 98
      web/ui/module/codemirror-promql/src/parser/path-finder.test.ts
  5. 54
      web/ui/module/codemirror-promql/src/parser/path-finder.ts
  6. 10
      web/ui/module/codemirror-promql/src/parser/type.ts
  7. 5
      web/ui/module/codemirror-promql/src/parser/vector.test.ts
  8. 42
      web/ui/module/codemirror-promql/src/parser/vector.ts
  9. 1
      web/ui/module/lezer-promql/src/highlight.js
  10. 111
      web/ui/module/lezer-promql/src/promql.grammar
  11. 465
      web/ui/module/lezer-promql/test/expression.txt
  12. 16
      web/ui/react-app/src/pages/graph/SeriesName.test.tsx
  13. 2
      web/ui/react-app/src/pages/graph/SeriesName.tsx

63
web/ui/module/codemirror-promql/src/complete/hybrid.ts

@ -19,30 +19,24 @@ import {
AggregateExpr,
And,
BinaryExpr,
BinModifiers,
Bool,
BoolModifier,
Div,
Duration,
Eql,
EqlRegex,
EqlSingle,
Expr,
FunctionCallArgs,
FunctionCallBody,
GroupingLabel,
GroupingLabels,
Gte,
Gtr,
Identifier,
LabelMatcher,
LabelMatchers,
LabelMatchList,
LabelName,
Lss,
Lte,
MatchOp,
MatrixSelector,
MetricIdentifier,
Identifier,
Mod,
Mul,
Neq,
@ -61,7 +55,7 @@ import {
} from '@prometheus-io/lezer-promql';
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { EditorState } from '@codemirror/state';
import { buildLabelMatchers, containsAtLeastOneChild, containsChild, retrieveAllRecursiveNodes, walkBackward, walkThrough } from '../parser';
import { buildLabelMatchers, containsAtLeastOneChild, containsChild, walkBackward } from '../parser';
import {
aggregateOpModifierTerms,
aggregateOpTerms,
@ -118,13 +112,13 @@ export interface Context {
function getMetricNameInVectorSelector(tree: SyntaxNode, state: EditorState): string {
// Find if there is a defined metric name. Should be used to autocomplete a labelValue or a labelName
// First find the parent "VectorSelector" to be able to find then the subChild "MetricIdentifier" if it exists.
// First find the parent "VectorSelector" to be able to find then the subChild "Identifier" if it exists.
let currentNode: SyntaxNode | null = walkBackward(tree, VectorSelector);
if (!currentNode) {
// Weird case that shouldn't happen, because "VectorSelector" is by definition the parent of the LabelMatchers.
return '';
}
currentNode = walkThrough(currentNode, MetricIdentifier, Identifier);
currentNode = currentNode.getChild(Identifier);
if (!currentNode) {
return '';
}
@ -193,7 +187,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
if (node.parent?.type.id === OffsetExpr) {
// we are likely in the given situation:
// `metric_name offset 5` that leads to this tree:
// `Expr(OffsetExpr(Expr(VectorSelector(MetricIdentifier(Identifier))),Offset,⚠))`
// `OffsetExpr(VectorSelector(Identifier),Offset,⚠)`
// Here we can just autocomplete a duration.
result.push({ kind: ContextKind.Duration });
break;
@ -219,7 +213,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
break;
}
// when we are in the situation 'metric_name !', we have the following tree
// Expr(VectorSelector(MetricIdentifier(Identifier),⚠))
// VectorSelector(Identifier,⚠)
// We should try to know if the char '!' is part of a binOp.
// Note: as it is quite experimental, maybe it requires more condition and to check the current tree (parent, other child at the same level ..etc.).
const operator = state.sliceDoc(node.from, node.to);
@ -247,13 +241,11 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
}
if (errorNodeParent?.type.id === VectorSelector) {
// it matches 'sum b'. So here we also have to autocomplete the aggregate operation modifier only
// if the associated metricIdentifier is matching an aggregation operation.
// if the associated identifier is matching an aggregation operation.
// Note: here is the corresponding tree in order to understand the situation:
// Expr(
// VectorSelector(
// MetricIdentifier(Identifier),
// ⚠(Identifier)
// )
// VectorSelector(
// Identifier,
// ⚠(Identifier)
// )
const operator = getMetricNameInVectorSelector(node, state);
if (aggregateOpTerms.filter((term) => term.label === operator).length > 0) {
@ -268,14 +260,13 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
break;
}
if (errorNodeParent && containsChild(errorNodeParent, Expr)) {
if (errorNodeParent && containsChild(errorNodeParent, 'Expr')) {
// this last case can appear with the following expression:
// 1. http_requests_total{method="GET"} off
// 2. rate(foo[5m]) un
// 3. sum(http_requests_total{method="GET"} off)
// For these different cases we have this kind of tree:
// Parent (
// Expr(),
// ⚠(Identifier)
// )
// We don't really care about the parent, here we are more interested if in the siblings of the error node, there is the node 'Expr'
@ -291,15 +282,15 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
// 2. sum(http_requests_total{method="GET"} / o) --> BinOpModifier + metric/function/aggregation
// Examples above give a different tree each time and ends up to be treated in this case.
// But they all have the following common tree pattern:
// Parent( Expr(...),
// Parent( ...,
// ... ,
// Expr(VectorSelector(MetricIdentifier(Identifier)))
// VectorSelector(Identifier)
// )
//
// So the first things to do is to get the `Parent` and to determinate if we are in this configuration.
// Otherwise we would just have to autocomplete the metric / function / aggregation.
const parent = node.parent?.parent?.parent?.parent;
const parent = node.parent?.parent;
if (!parent) {
// this case can be possible if the topNode is not anymore PromQL but MetricName.
// In this particular case, then we just want to autocomplete the metric
@ -307,7 +298,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
break;
}
// now we have to know if we have two Expr in the direct children of the `parent`
const containExprTwice = containsChild(parent, Expr, Expr);
const containExprTwice = containsChild(parent, 'Expr', 'Expr');
if (containExprTwice) {
if (parent.type.id === BinaryExpr && !containsAtLeastOneChild(parent, 0)) {
// We are likely in the case 1 or 5
@ -320,11 +311,10 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
);
// in case the BinaryExpr is a comparison, we should autocomplete the `bool` keyword. But only if it is not present.
// When the `bool` keyword is NOT present, then the expression looks like this:
// BinaryExpr( Expr(...), Gtr , BinModifiers, Expr(...) )
// BinaryExpr( ..., Gtr , ... )
// When the `bool` keyword is present, then the expression looks like this:
// BinaryExpr( Expr(...), Gtr , BinModifiers(Bool), Expr(...) )
// To know if it is not present, we just have to check if the Bool is not present as a child of the BinModifiers.
if (containsAtLeastOneChild(parent, Eql, Gte, Gtr, Lte, Lss, Neq) && !walkThrough(parent, BinModifiers, Bool)) {
// BinaryExpr( ..., Gtr , BoolModifier(...), ... )
if (containsAtLeastOneChild(parent, Eql, Gte, Gtr, Lte, Lss, Neq) && !containsAtLeastOneChild(parent, BoolModifier)) {
result.push({ kind: ContextKind.Bool });
}
}
@ -334,7 +324,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
{ kind: ContextKind.Function },
{ kind: ContextKind.Aggregation }
);
if (parent.type.id !== FunctionCallArgs && parent.type.id !== MatrixSelector) {
if (parent.type.id !== FunctionCallBody && parent.type.id !== MatrixSelector) {
// it's too avoid to autocomplete a number in situation where it shouldn't.
// Like with `sum by(rat)`
result.push({ kind: ContextKind.Number });
@ -365,7 +355,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
result.push({ kind: ContextKind.LabelName, metricName: getMetricNameInVectorSelector(node, state) });
break;
case LabelName:
if (node.parent?.type.id === GroupingLabel) {
if (node.parent?.type.id === GroupingLabels) {
// In this case we are in the given situation:
// sum by (myL)
// So we have to continue to autocomplete any kind of labelName
@ -393,7 +383,8 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
// then find the metricName if it exists
const metricName = getMetricNameInVectorSelector(node, state);
// finally get the full matcher available
const labelMatchers = buildLabelMatchers(retrieveAllRecursiveNodes(walkBackward(node, LabelMatchList), LabelMatchList, LabelMatcher), state);
const matcherNode = walkBackward(node, LabelMatchers);
const labelMatchers = buildLabelMatchers(matcherNode ? matcherNode.getChildren(LabelMatcher) : [], state);
result.push({
kind: ContextKind.LabelValue,
metricName: metricName,
@ -407,10 +398,10 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
// Here we are likely in this situation:
// `go[5d:4]`
// and we have the given tree:
// Expr( SubqueryExpr(
// Expr(VectorSelector(MetricIdentifier(Identifier))),
// Duration, Duration, ⚠(NumberLiteral)
// ))
// SubqueryExpr(
// VectorSelector(Identifier),
// Duration, Duration, ⚠(NumberLiteral)
// )
// So we should continue to autocomplete a duration
result.push({ kind: ContextKind.Duration });
} else {

2
web/ui/module/codemirror-promql/src/parser/index.ts

@ -13,4 +13,4 @@
export { buildLabelMatchers, labelMatchersToString } from './matcher';
export { Parser } from './parser';
export { walkBackward, walkThrough, containsAtLeastOneChild, containsChild, retrieveAllRecursiveNodes } from './path-finder';
export { walkBackward, containsAtLeastOneChild, containsChild } from './path-finder';

56
web/ui/module/codemirror-promql/src/parser/parser.ts

@ -17,26 +17,21 @@ import {
AggregateExpr,
And,
BinaryExpr,
BinModifiers,
Bool,
BoolModifier,
Bottomk,
CountValues,
Eql,
EqlSingle,
Expr,
FunctionCall,
FunctionCallArgs,
FunctionCallBody,
Gte,
Gtr,
Identifier,
LabelMatcher,
LabelMatchers,
LabelMatchList,
Lss,
Lte,
MatrixSelector,
MetricIdentifier,
Neq,
Or,
ParenExpr,
@ -48,7 +43,7 @@ import {
Unless,
VectorSelector,
} from '@prometheus-io/lezer-promql';
import { containsAtLeastOneChild, retrieveAllRecursiveNodes, walkThrough } from './path-finder';
import { containsAtLeastOneChild } from './path-finder';
import { getType } from './type';
import { buildLabelMatchers } from './matcher';
import { EditorState } from '@codemirror/state';
@ -105,8 +100,6 @@ export class Parser {
return ValueType.none;
}
switch (node.type.id) {
case Expr:
return this.checkAST(node.firstChild);
case AggregateExpr:
this.checkAggregationExpr(node);
break;
@ -117,28 +110,28 @@ export class Parser {
this.checkCallFunction(node);
break;
case ParenExpr:
this.checkAST(walkThrough(node, Expr));
this.checkAST(node.getChild('Expr'));
break;
case UnaryExpr:
const unaryExprType = this.checkAST(walkThrough(node, Expr));
const unaryExprType = this.checkAST(node.getChild('Expr'));
if (unaryExprType !== ValueType.scalar && unaryExprType !== ValueType.vector) {
this.addDiagnostic(node, `unary expression only allowed on expressions of type scalar or instant vector, got ${unaryExprType}`);
}
break;
case SubqueryExpr:
const subQueryExprType = this.checkAST(walkThrough(node, Expr));
const subQueryExprType = this.checkAST(node.getChild('Expr'));
if (subQueryExprType !== ValueType.vector) {
this.addDiagnostic(node, `subquery is only allowed on instant vector, got ${subQueryExprType} in ${node.name} instead`);
}
break;
case MatrixSelector:
this.checkAST(walkThrough(node, Expr));
this.checkAST(node.getChild('Expr'));
break;
case VectorSelector:
this.checkVectorSelector(node);
break;
case StepInvariantExpr:
const exprValue = this.checkAST(walkThrough(node, Expr));
const exprValue = this.checkAST(node.getChild('Expr'));
if (exprValue !== ValueType.vector && exprValue !== ValueType.matrix) {
this.addDiagnostic(node, `@ modifier must be preceded by an instant selector vector or range vector selector or a subquery`);
}
@ -164,27 +157,19 @@ export class Parser {
this.addDiagnostic(node, 'aggregation operator expected in aggregation expression but got nothing');
return;
}
const expr = walkThrough(node, FunctionCallBody, FunctionCallArgs, Expr);
if (!expr) {
const body = node.getChild(FunctionCallBody);
const params = body ? body.getChildren('Expr') : [];
if (!params.length) {
this.addDiagnostic(node, 'unable to find the parameter for the expression');
return;
}
this.expectType(expr, ValueType.vector, 'aggregation expression');
this.expectType(params[params.length - 1], ValueType.vector, 'aggregation expression');
// get the parameter of the aggregation operator
const params = walkThrough(node, FunctionCallBody, FunctionCallArgs, FunctionCallArgs, Expr);
if (aggregateOp.type.id === Topk || aggregateOp.type.id === Bottomk || aggregateOp.type.id === Quantile) {
if (!params) {
this.addDiagnostic(node, 'no parameter found');
return;
}
this.expectType(params, ValueType.scalar, 'aggregation parameter');
this.expectType(params[0], ValueType.scalar, 'aggregation parameter');
}
if (aggregateOp.type.id === CountValues) {
if (!params) {
this.addDiagnostic(node, 'no parameter found');
return;
}
this.expectType(params, ValueType.string, 'aggregation parameter');
this.expectType(params[0], ValueType.string, 'aggregation parameter');
}
}
@ -200,7 +185,7 @@ export class Parser {
}
const lt = this.checkAST(lExpr);
const rt = this.checkAST(rExpr);
const boolModifierUsed = walkThrough(node, BinModifiers, Bool);
const boolModifierUsed = node.getChild(BoolModifier);
const isComparisonOperator = containsAtLeastOneChild(node, Eql, Neq, Lte, Lss, Gte, Gtr);
const isSetOperator = containsAtLeastOneChild(node, And, Or, Unless);
@ -259,7 +244,8 @@ export class Parser {
return;
}
const args = retrieveAllRecursiveNodes(walkThrough(node, FunctionCallBody), FunctionCallArgs, Expr);
const body = node.getChild(FunctionCallBody);
const args = body ? body.getChildren('Expr') : [];
const funcSignature = getFunction(funcID.type.id);
const nargs = funcSignature.argTypes.length;
@ -295,14 +281,12 @@ export class Parser {
}
private checkVectorSelector(node: SyntaxNode): void {
const labelMatchers = buildLabelMatchers(
retrieveAllRecursiveNodes(walkThrough(node, LabelMatchers, LabelMatchList), LabelMatchList, LabelMatcher),
this.state
);
const matchList = node.getChild(LabelMatchers);
const labelMatchers = buildLabelMatchers(matchList ? matchList.getChildren(LabelMatcher) : [], this.state);
let vectorSelectorName = '';
// VectorSelector ( MetricIdentifier ( Identifier ) )
// VectorSelector ( Identifier )
// https://github.com/promlabs/lezer-promql/blob/71e2f9fa5ae6f5c5547d5738966cd2512e6b99a8/src/promql.grammar#L200
const vectorSelectorNodeName = walkThrough(node, MetricIdentifier, Identifier);
const vectorSelectorNodeName = node.getChild(Identifier);
if (vectorSelectorNodeName) {
vectorSelectorName = this.state.sliceDoc(vectorSelectorNodeName.from, vectorSelectorNodeName.to);
}

98
web/ui/module/codemirror-promql/src/parser/path-finder.test.ts

@ -17,9 +17,6 @@ import {
BinaryExpr,
Div,
Eql,
Expr,
FunctionCall,
FunctionCallArgs,
FunctionCallBody,
Gte,
Gtr,
@ -28,74 +25,14 @@ import {
Mod,
Mul,
Neq,
NumberLiteral,
Sub,
VectorSelector,
} from '@prometheus-io/lezer-promql';
import { createEditorState } from '../test/utils-test';
import { containsAtLeastOneChild, containsChild, retrieveAllRecursiveNodes, walkBackward, walkThrough } from './path-finder';
import { containsAtLeastOneChild, containsChild, walkBackward } from './path-finder';
import { SyntaxNode } from '@lezer/common';
import { syntaxTree } from '@codemirror/language';
describe('walkThrough test', () => {
const testCases = [
{
title: 'should return the node when no path is given',
expr: '1 > bool 2',
pos: 0,
expectedNode: 'PromQL',
path: [] as number[],
expectedDoc: '1 > bool 2',
},
{
title: 'should find the path',
expr: "100 * (1 - avg by(instance)(irate(node_cpu{mode='idle'}[5m])))",
pos: 11,
path: [Expr, NumberLiteral],
// for the moment the function walkThrough is not able to find the following path.
// That's because the function is iterating through the tree by searching the first possible node that matched
// the node ID path[i].
// So for the current expression, and the given position we are in the sub expr (1 - avg ...).
// Expr is matching 1 and not avg.
// TODO fix this issue
// path: [Expr, AggregateExpr, AggregateOp, Avg],
expectedNode: NumberLiteral,
expectedDoc: '1',
},
{
title: 'should not find the path',
expr: 'topk(10, count by (job)({__name__=~".+"}))',
pos: 12,
path: [Expr, BinaryExpr],
expectedNode: undefined,
expectedDoc: undefined,
},
{
title: 'should find a node in a recursive node definition',
expr: 'rate(1, 2, 3)',
pos: 0,
path: [Expr, FunctionCall, FunctionCallBody, FunctionCallArgs, FunctionCallArgs, Expr, NumberLiteral],
expectedNode: NumberLiteral,
expectedDoc: '2',
},
];
testCases.forEach((value) => {
it(value.title, () => {
const state = createEditorState(value.expr);
const subTree = syntaxTree(state).resolve(value.pos, -1);
const node = walkThrough(subTree, ...value.path);
if (typeof value.expectedNode === 'number') {
expect(value.expectedNode).toEqual(node?.type.id);
} else {
expect(value.expectedNode).toEqual(node?.type.name);
}
if (node) {
expect(value.expectedDoc).toEqual(state.sliceDoc(node.from, node.to));
}
});
});
});
describe('containsAtLeastOneChild test', () => {
const testCases = [
{
@ -103,14 +40,13 @@ describe('containsAtLeastOneChild test', () => {
expr: '1 > 2',
pos: 3,
expectedResult: false,
walkThrough: [],
child: [],
},
{
title: 'should find a node in the given list',
expr: '1 > 2',
pos: 0,
walkThrough: [Expr, BinaryExpr],
take: BinaryExpr,
child: [Eql, Neq, Lte, Lss, Gte, Gtr],
expectedResult: true,
},
@ -118,7 +54,7 @@ describe('containsAtLeastOneChild test', () => {
title: 'should not find a node in the given list',
expr: '1 > 2',
pos: 0,
walkThrough: [Expr, BinaryExpr],
take: BinaryExpr,
child: [Mul, Div, Mod, Add, Sub],
expectedResult: false,
},
@ -127,7 +63,7 @@ describe('containsAtLeastOneChild test', () => {
it(value.title, () => {
const state = createEditorState(value.expr);
const subTree = syntaxTree(state).resolve(value.pos, -1);
const node = walkThrough(subTree, ...value.walkThrough);
const node = value.take == null ? subTree : subTree.getChild(value.take);
expect(node).toBeTruthy();
if (node) {
expect(value.expectedResult).toEqual(containsAtLeastOneChild(node, ...value.child));
@ -143,24 +79,25 @@ describe('containsChild test', () => {
expr: 'metric_name / ignor',
pos: 0,
expectedResult: true,
walkThrough: [Expr, BinaryExpr],
child: [Expr, Expr],
walkThrough: [BinaryExpr],
child: ['Expr', 'Expr'],
},
{
title: 'Should not find all child required',
expr: 'sum(ra)',
pos: 0,
expectedResult: false,
walkThrough: [Expr, AggregateExpr, FunctionCallBody, FunctionCallArgs],
child: [Expr, Expr],
walkThrough: [AggregateExpr, FunctionCallBody],
child: ['Expr', 'Expr'],
},
];
testCases.forEach((value) => {
it(value.title, () => {
const state = createEditorState(value.expr);
const subTree = syntaxTree(state).resolve(value.pos, -1);
const node: SyntaxNode | null = walkThrough(subTree, ...value.walkThrough);
let node: SyntaxNode | null | undefined = syntaxTree(state).resolve(value.pos, -1);
for (const enter of value.walkThrough) {
node = node?.getChild(enter);
}
expect(node).toBeTruthy();
if (node) {
expect(value.expectedResult).toEqual(containsChild(node, ...value.child));
@ -169,17 +106,6 @@ describe('containsChild test', () => {
});
});
describe('retrieveAllRecursiveNodes test', () => {
it('should find every occurrence', () => {
const state = createEditorState('rate(1,2,3)');
const tree = syntaxTree(state).topNode.firstChild;
expect(tree).toBeTruthy();
if (tree) {
expect(3).toEqual(retrieveAllRecursiveNodes(walkThrough(tree, FunctionCall, FunctionCallBody), FunctionCallArgs, Expr).length);
}
});
});
describe('walkbackward test', () => {
const testCases = [
{

54
web/ui/module/codemirror-promql/src/parser/path-finder.ts

@ -15,36 +15,12 @@ import { SyntaxNode } from '@lezer/common';
// walkBackward will iterate other the tree from the leaf to the root until it founds the given `exit` node.
// It returns null if the exit is not found.
export function walkBackward(node: SyntaxNode, exit: number): SyntaxNode | null {
const cursor = node.cursor();
let cursorIsMoving = true;
while (cursorIsMoving && cursor.type.id !== exit) {
cursorIsMoving = cursor.parent();
}
return cursor.type.id === exit ? cursor.node : null;
}
// walkThrough is going to follow the path passed in parameter.
// If it succeeds to reach the last id/name of the path, then it will return the corresponding Subtree.
// Otherwise if it's not possible to reach the last id/name of the path, it will return `null`
// Note: the way followed during the iteration of the tree to find the given path, is only from the root to the leaf.
export function walkThrough(node: SyntaxNode, ...path: (number | string)[]): SyntaxNode | null {
const cursor = node.cursor();
let i = 0;
let cursorIsMoving = true;
path.unshift(cursor.type.id);
while (i < path.length && cursorIsMoving) {
if (cursor.type.id === path[i] || cursor.type.name === path[i]) {
i++;
if (i < path.length) {
cursorIsMoving = cursor.next();
}
} else {
cursorIsMoving = cursor.nextSibling();
export function walkBackward(node: SyntaxNode | null, exit: number): SyntaxNode | null {
for (;;) {
if (!node || node.type.id === exit) {
return node;
}
}
if (i >= path.length) {
return cursor.node;
node = node.parent;
}
return null;
}
@ -73,28 +49,10 @@ export function containsChild(node: SyntaxNode, ...child: (number | string)[]):
let i = 0;
do {
if (cursor.type.id === child[i] || cursor.type.name === child[i]) {
if (cursor.type.is(child[i])) {
i++;
}
} while (i < child.length && cursor.nextSibling());
return i >= child.length;
}
export function retrieveAllRecursiveNodes(parentNode: SyntaxNode | null, recursiveNode: number, leaf: number): SyntaxNode[] {
const nodes: SyntaxNode[] = [];
function recursiveRetrieveNode(node: SyntaxNode | null, nodes: SyntaxNode[]) {
const subNode = node?.getChild(recursiveNode);
const le = node?.lastChild;
if (subNode && subNode.type.id === recursiveNode) {
recursiveRetrieveNode(subNode, nodes);
}
if (le && le.type.id === leaf) {
nodes.push(le);
}
}
recursiveRetrieveNode(parentNode, nodes);
return nodes;
}

10
web/ui/module/codemirror-promql/src/parser/type.ts

@ -15,7 +15,6 @@ import { SyntaxNode } from '@lezer/common';
import {
AggregateExpr,
BinaryExpr,
Expr,
FunctionCall,
MatrixSelector,
NumberLiteral,
@ -27,7 +26,6 @@ import {
UnaryExpr,
VectorSelector,
} from '@prometheus-io/lezer-promql';
import { walkThrough } from './path-finder';
import { getFunction, ValueType } from '../types';
// Based on https://github.com/prometheus/prometheus/blob/d668a7efe3107dbdcc67bf4e9f12430ed8e2b396/promql/parser/ast.go#L191
@ -36,8 +34,6 @@ export function getType(node: SyntaxNode | null): ValueType {
return ValueType.none;
}
switch (node.type.id) {
case Expr:
return getType(node.firstChild);
case AggregateExpr:
return ValueType.vector;
case VectorSelector:
@ -53,9 +49,9 @@ export function getType(node: SyntaxNode | null): ValueType {
case SubqueryExpr:
return ValueType.matrix;
case ParenExpr:
return getType(walkThrough(node, Expr));
return getType(node.getChild('Expr'));
case UnaryExpr:
return getType(walkThrough(node, Expr));
return getType(node.getChild('Expr'));
case BinaryExpr:
const lt = getType(node.firstChild);
const rt = getType(node.lastChild);
@ -70,7 +66,7 @@ export function getType(node: SyntaxNode | null): ValueType {
}
return getFunction(funcNode.type.id).returnType;
case StepInvariantExpr:
return getType(walkThrough(node, Expr));
return getType(node.getChild('Expr'));
default:
return ValueType.none;
}

5
web/ui/module/codemirror-promql/src/parser/vector.test.ts

@ -13,8 +13,7 @@
import { buildVectorMatching } from './vector';
import { createEditorState } from '../test/utils-test';
import { walkThrough } from './path-finder';
import { BinaryExpr, Expr } from '@prometheus-io/lezer-promql';
import { BinaryExpr } from '@prometheus-io/lezer-promql';
import { syntaxTree } from '@codemirror/language';
import { VectorMatchCardinality } from '../types';
@ -201,7 +200,7 @@ describe('buildVectorMatching test', () => {
testCases.forEach((value) => {
it(value.binaryExpr, () => {
const state = createEditorState(value.binaryExpr);
const node = walkThrough(syntaxTree(state).topNode, Expr, BinaryExpr);
const node = syntaxTree(state).topNode.getChild(BinaryExpr);
expect(node).toBeTruthy();
if (node) {
expect(value.expectedVectorMatching).toEqual(buildVectorMatching(state, node));

42
web/ui/module/codemirror-promql/src/parser/vector.ts

@ -16,19 +16,17 @@ import { SyntaxNode } from '@lezer/common';
import {
And,
BinaryExpr,
BinModifiers,
GroupingLabel,
GroupingLabelList,
MatchingModifierClause,
LabelName,
GroupingLabels,
GroupLeft,
GroupRight,
On,
OnOrIgnoring,
Or,
Unless,
} from '@prometheus-io/lezer-promql';
import { VectorMatchCardinality, VectorMatching } from '../types';
import { containsAtLeastOneChild, retrieveAllRecursiveNodes } from './path-finder';
import { containsAtLeastOneChild } from './path-finder';
export function buildVectorMatching(state: EditorState, binaryNode: SyntaxNode): VectorMatching | null {
if (!binaryNode || binaryNode.type.id !== BinaryExpr) {
@ -40,28 +38,24 @@ export function buildVectorMatching(state: EditorState, binaryNode: SyntaxNode):
on: false,
include: [],
};
const binModifiers = binaryNode.getChild(BinModifiers);
if (binModifiers) {
const onOrIgnoring = binModifiers.getChild(OnOrIgnoring);
if (onOrIgnoring) {
result.on = onOrIgnoring.getChild(On) !== null;
const labels = retrieveAllRecursiveNodes(onOrIgnoring.getChild(GroupingLabels), GroupingLabelList, GroupingLabel);
if (labels.length > 0) {
for (const label of labels) {
result.matchingLabels.push(state.sliceDoc(label.from, label.to));
}
}
const modifierClause = binaryNode.getChild(MatchingModifierClause);
if (modifierClause) {
result.on = modifierClause.getChild(On) !== null;
const labelNode = modifierClause.getChild(GroupingLabels);
const labels = labelNode ? labelNode.getChildren(LabelName) : [];
for (const label of labels) {
result.matchingLabels.push(state.sliceDoc(label.from, label.to));
}
const groupLeft = binModifiers.getChild(GroupLeft);
const groupRight = binModifiers.getChild(GroupRight);
if (groupLeft || groupRight) {
const groupLeft = modifierClause.getChild(GroupLeft);
const groupRight = modifierClause.getChild(GroupRight);
const group = groupLeft || groupRight;
if (group) {
result.card = groupLeft ? VectorMatchCardinality.CardManyToOne : VectorMatchCardinality.CardOneToMany;
const includeLabels = retrieveAllRecursiveNodes(binModifiers.getChild(GroupingLabels), GroupingLabelList, GroupingLabel);
if (includeLabels.length > 0) {
for (const label of includeLabels) {
result.include.push(state.sliceDoc(label.from, label.to));
}
const labelNode = group.nextSibling;
const labels = labelNode?.getChildren(LabelName) || [];
for (const label of labels) {
result.include.push(state.sliceDoc(label.from, label.to));
}
}
}

1
web/ui/module/lezer-promql/src/highlight.js

@ -19,6 +19,7 @@ export const promQLHighLight = styleTags({
StringLiteral: tags.string,
NumberLiteral: tags.number,
Duration: tags.number,
Identifier: tags.variableName,
'Abs Absent AbsentOverTime Acos Acosh Asin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramQuantile HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year':
tags.function(tags.variableName),
'Avg Bottomk Count Count_values Group Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword,

111
web/ui/module/lezer-promql/src/promql.grammar

@ -11,10 +11,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
@top PromQL { Expr }
@top MetricName { MetricIdentifier }
@top PromQL { expr }
@top MetricName { Identifier }
@precedence {
group,
pow @right,
mul @left
add @left,
@ -23,7 +24,7 @@
or @left
}
Expr {
expr[@isGroup=Expr] {
AggregateExpr |
BinaryExpr |
FunctionCall |
@ -65,53 +66,38 @@ AggregateModifier {
}
BinaryExpr {
Expr !pow Pow BinModifiers Expr |
Expr !mul Mul BinModifiers Expr |
Expr !mul Div BinModifiers Expr |
Expr !mul Mod BinModifiers Expr |
Expr !mul Atan2 BinModifiers Expr |
Expr !add Add BinModifiers Expr |
Expr !add Sub BinModifiers Expr |
Expr !eql Eql BinModifiers Expr |
Expr !eql Gte BinModifiers Expr |
Expr !eql Gtr BinModifiers Expr |
Expr !eql Lte BinModifiers Expr |
Expr !eql Lss BinModifiers Expr |
Expr !eql Neq BinModifiers Expr |
Expr !and And BinModifiers Expr |
Expr !and Unless BinModifiers Expr |
Expr !or Or BinModifiers Expr
expr !pow Pow binModifiers expr |
expr !mul Mul binModifiers expr |
expr !mul Div binModifiers expr |
expr !mul Mod binModifiers expr |
expr !mul Atan2 binModifiers expr |
expr !add Add binModifiers expr |
expr !add Sub binModifiers expr |
expr !eql Eql binModifiers expr |
expr !eql Gte binModifiers expr |
expr !eql Gtr binModifiers expr |
expr !eql Lte binModifiers expr |
expr !eql Lss binModifiers expr |
expr !eql Neq binModifiers expr |
expr !and And binModifiers expr |
expr !and Unless binModifiers expr |
expr !or Or binModifiers expr
}
OnOrIgnoring {
Ignoring GroupingLabels |
On GroupingLabels
MatchingModifierClause {
(Ignoring | On) GroupingLabels
((GroupLeft | GroupRight) (!group GroupingLabels)?)?
}
BinModifiers {
Bool?
(
OnOrIgnoring
(
(GroupLeft | GroupRight)
(!mul GroupingLabels)? // TODO: Is the "!mul" here correct? Inserted it to resolve a shift/reduce conflict because we always want to count opening parenthesis after this to be counted toward this modifier, not toward a next sub-expression.
)?
)?
}
GroupingLabels {
"(" GroupingLabelList ")" |
"(" GroupingLabelList "," ")" |
"(" ")"
}
BoolModifier { Bool }
GroupingLabelList {
GroupingLabelList "," GroupingLabel |
GroupingLabel
binModifiers {
BoolModifier?
MatchingModifierClause?
}
GroupingLabel {
LabelName
GroupingLabels {
"(" (LabelName ("," LabelName)* ","?)? ")"
}
FunctionCall {
@ -189,34 +175,28 @@ FunctionIdentifier {
}
FunctionCallBody {
"(" FunctionCallArgs ")" |
"(" ")"
}
FunctionCallArgs {
FunctionCallArgs "," Expr |
Expr
"(" (expr ("," expr)*)? ")"
}
ParenExpr {
"(" Expr ")"
"(" expr ")"
}
OffsetExpr {
Expr Offset Sub? Duration
expr Offset Sub? Duration
}
MatrixSelector {
// TODO: Can this not be more specific than "Expr"?
Expr "[" Duration "]"
// TODO: Can this not be more specific than "expr"?
expr "[" Duration "]"
}
SubqueryExpr {
Expr "[" Duration ":" ("" | Duration) "]"
expr "[" Duration ":" ("" | Duration) "]"
}
UnaryExpr {
!mul UnaryOp~signed Expr
!mul UnaryOp~signed expr
}
UnaryOp {
@ -225,20 +205,13 @@ UnaryOp {
}
VectorSelector {
MetricIdentifier LabelMatchers |
MetricIdentifier |
Identifier LabelMatchers |
Identifier |
LabelMatchers
}
LabelMatchers {
"{" LabelMatchList "}" |
"{" LabelMatchList "," "}" |
"{" "}"
}
LabelMatchList {
LabelMatchList "," LabelMatcher |
LabelMatcher
"{" (LabelMatcher ("," LabelMatcher)* ","?)? "}"
}
MatchOp {
@ -252,12 +225,8 @@ LabelMatcher {
LabelName MatchOp StringLiteral
}
MetricIdentifier {
Identifier
}
StepInvariantExpr {
Expr At ( NumberLiteral | AtModifierPreprocessors "(" ")" )
expr At ( NumberLiteral | AtModifierPreprocessors "(" ")" )
}
AtModifierPreprocessors {

465
web/ui/module/lezer-promql/test/expression.txt

@ -4,7 +4,7 @@
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Double-quoted string literal
@ -12,7 +12,7 @@ PromQL(Expr(NumberLiteral))
==>
PromQL(Expr(StringLiteral))
PromQL(StringLiteral)
# Single-quoted string literal
@ -20,7 +20,7 @@ PromQL(Expr(StringLiteral))
==>
PromQL(Expr(StringLiteral))
PromQL(StringLiteral)
# Backtick-quoted string literal
@ -28,7 +28,7 @@ PromQL(Expr(StringLiteral))
==>
PromQL(Expr(StringLiteral))
PromQL(StringLiteral)
# Backtick-quoted multi-line string literal
@ -38,7 +38,7 @@ string`
==>
PromQL(Expr(StringLiteral))
PromQL(StringLiteral)
# Addition
@ -46,7 +46,7 @@ PromQL(Expr(StringLiteral))
==>
PromQL(Expr(BinaryExpr(Expr(NumberLiteral), Add, BinModifiers, Expr(NumberLiteral))))
PromQL(BinaryExpr(NumberLiteral, Add, NumberLiteral))
# Complex expression
@ -55,101 +55,61 @@ sum by(job, mode) (rate(node_cpu_seconds_total[1m])) / on(job) group_left sum by
==>
PromQL(
Expr(
BinaryExpr(
Expr(
AggregateExpr(
AggregateOp(Sum),
AggregateModifier(
By,
GroupingLabels(
GroupingLabelList(
GroupingLabelList(
GroupingLabel(LabelName)
),
GroupingLabel(LabelName)
)
LabelName,
LabelName
)
),
FunctionCallBody(
FunctionCallArgs(
Expr(
FunctionCall(
FunctionIdentifier(Rate),
FunctionCallBody(
FunctionCallArgs(
Expr(
MatrixSelector(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
),
),
Duration
)
)
)
)
)
)
)
)
)
),
Div,
BinModifiers(
OnOrIgnoring(
MatchingModifierClause(
On,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
GroupLeft
),
GroupLeft
),
Expr(
AggregateExpr(
AggregateOp(Sum),
AggregateModifier(
By,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
),
FunctionCallBody(
FunctionCallArgs(
Expr(
FunctionCall(
FunctionIdentifier(Rate),
FunctionCallBody(
FunctionCallArgs(
Expr(
MatrixSelector(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
),
),
Duration
)
)
)
)
)
)
)
)
)
)
)
)
)
# Case insensitivity for aggregations and binop modifiers.
@ -159,66 +119,42 @@ SuM BY(testlabel1) (testmetric1) / IGNOring(testlabel2) AVG withOUT(testlabel3)
==>
PromQL(
Expr(
BinaryExpr(
Expr(
AggregateExpr(
AggregateOp(Sum),
AggregateModifier(
By,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
),
FunctionCallBody(
FunctionCallArgs(
Expr(
VectorSelector(
MetricIdentifier(Identifier)
Identifier
)
)
)
)
)
),
),
Div,
BinModifiers(
OnOrIgnoring(
MatchingModifierClause(
Ignoring,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
)
),
Expr(
),
AggregateExpr(
AggregateOp(Avg),
AggregateModifier(
Without,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
),
FunctionCallBody(
FunctionCallArgs(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
)
)
)
)
)
)
)
@ -229,85 +165,40 @@ metric1 and metric2 AND metric3 unless metric4 UNLESS metric5 or metric6 OR metr
==>
PromQL(
Expr(
BinaryExpr(
BinaryExpr(
Expr(
BinaryExpr(
BinaryExpr(
Expr(
BinaryExpr(
BinaryExpr(
Expr(
BinaryExpr(
Expr(
BinaryExpr(
Expr(
BinaryExpr(
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
),
And,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
),
And,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
),
Unless,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
),
Unless,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
VectorSelector(Identifier),
And,
VectorSelector(Identifier)
),
And,
VectorSelector(Identifier)
),
Or,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
Unless,
VectorSelector(Identifier)
),
Unless,
VectorSelector(Identifier)
),
Or,
BinModifiers,
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
)
)
VectorSelector(Identifier)
),
Or,
VectorSelector(Identifier)
)
)
# Duration units
foo[1y2w3d4h5m6s7ms]
==>
PromQL(Expr(MatrixSelector(Expr(VectorSelector(MetricIdentifier(Identifier))),Duration)))
PromQL(MatrixSelector(VectorSelector(Identifier),Duration))
# Incorrectly ordered duration units
@ -315,7 +206,7 @@ foo[1m2h]
==>
PromQL(Expr(SubqueryExpr(Expr(VectorSelector(MetricIdentifier(Identifier))),Duration,⚠,Duration)))
PromQL(SubqueryExpr(VectorSelector(Identifier),Duration,⚠,Duration))
# Using a function name as a metric name
@ -323,7 +214,7 @@ rate
==>
PromQL(Expr(VectorSelector(MetricIdentifier(Identifier))))
PromQL(VectorSelector(Identifier))
# Match operators
@ -332,41 +223,31 @@ metric_name{a="1",b!="2",c=~"3",d!~"4"}
==>
PromQL(
Expr(
VectorSelector(
MetricIdentifier(Identifier),
Identifier,
LabelMatchers(
LabelMatchList(
LabelMatchList(
LabelMatchList(
LabelMatchList(
LabelMatcher(
LabelName,
MatchOp(EqlSingle),
StringLiteral
)
),
LabelMatcher(
LabelName,
MatchOp(Neq),
StringLiteral
)
),
LabelMatcher(
LabelName,
MatchOp(EqlRegex),
StringLiteral
)
),
LabelMatcher(
LabelName,
MatchOp(NeqRegex),
StringLiteral
)
LabelMatcher(
LabelName,
MatchOp(EqlSingle),
StringLiteral
),
LabelMatcher(
LabelName,
MatchOp(Neq),
StringLiteral
),
LabelMatcher(
LabelName,
MatchOp(EqlRegex),
StringLiteral
),
LabelMatcher(
LabelName,
MatchOp(NeqRegex),
StringLiteral
)
)
)
)
)
# Binary expression with bool modifier
@ -376,18 +257,14 @@ metric_name > bool 1
==>
PromQL(
Expr(
BinaryExpr(
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
),
Identifier
),
Gtr,
BinModifiers(Bool),
Expr(NumberLiteral)
BoolModifier(Bool),
NumberLiteral
)
)
)
# Binary expression with group_x() labels.
@ -397,41 +274,25 @@ metric1 + on(foo) group_left(bar, baz) metric2
==>
PromQL(
Expr(
BinaryExpr(
Expr(
VectorSelector(
MetricIdentifier(Identifier)
)
),
Identifier
),
Add,
BinModifiers(
OnOrIgnoring(
MatchingModifierClause(
On,
GroupingLabels(
GroupingLabelList(
GroupingLabel(LabelName)
)
LabelName
)
),
GroupLeft,
GroupingLabels(
GroupingLabelList(
GroupingLabelList(
GroupingLabel(LabelName)
),
GroupingLabel(LabelName)
GroupLeft,
GroupingLabels(
LabelName,
LabelName
)
)
),
Expr(
),
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
)
)
)
@ -441,27 +302,17 @@ last_over_time(data[1m])
==>
PromQL(
Expr(
FunctionCall(
FunctionIdentifier(LastOverTime),
FunctionCallBody(
FunctionCallArgs(
Expr(
MatrixSelector(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
),
),
Duration
)
)
)
)
)
)
)
# Function sgn
@ -470,21 +321,13 @@ sgn(data)
==>
PromQL(
Expr(
FunctionCall(
FunctionIdentifier(Sgn),
FunctionCallBody(
FunctionCallArgs(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
)
)
)
)
)
@ -494,28 +337,14 @@ clamp(data,0,1)
==>
PromQL(
Expr(
FunctionCall(
FunctionIdentifier(Clamp),
FunctionCallBody(
FunctionCallArgs(
FunctionCallArgs(
FunctionCallArgs(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
Expr(NumberLiteral)
),
Expr(NumberLiteral)
)
VectorSelector(Identifier),
NumberLiteral,
NumberLiteral
)
)
)
)
# Metric start
@ -523,14 +352,14 @@ PromQL(
start
==>
PromQL(Expr(VectorSelector(MetricIdentifier(Identifier))))
PromQL(VectorSelector(Identifier))
# Metric end
end
==>
PromQL(Expr(VectorSelector(MetricIdentifier(Identifier))))
PromQL(VectorSelector(Identifier))
# Simple At start
@ -538,19 +367,13 @@ foo @ start()
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
AtModifierPreprocessors(Start),
)
)
)
# Simple At end
@ -559,19 +382,13 @@ foo @ end()
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
AtModifierPreprocessors(End),
)
)
)
# Simple At number
@ -580,18 +397,12 @@ foo @ 1234
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
NumberLiteral
)
)
)
@ -601,19 +412,13 @@ foo @ start( )
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
AtModifierPreprocessors(Start),
)
)
)
# Complex test with At modifier
@ -624,60 +429,34 @@ topk(7, rate(process_cpu_seconds_total[1h] @ 1234))
==>
PromQL(
Expr(
BinaryExpr(
Expr(
FunctionCall(
FunctionIdentifier(Rate),
FunctionCallBody(
FunctionCallArgs(
Expr(
MatrixSelector(
Expr(VectorSelector(MetricIdentifier(Identifier))),
VectorSelector(Identifier),
Duration
)
)
)
)
)
),
),
And,
BinModifiers,
Expr(
AggregateExpr(
AggregateOp(Topk),
FunctionCallBody(
FunctionCallArgs(
FunctionCallArgs(Expr(NumberLiteral)),
Expr(
FunctionCall(
FunctionIdentifier(Rate),
FunctionCallBody(
FunctionCallArgs(
Expr(
StepInvariantExpr(
Expr(
MatrixSelector(
Expr(
VectorSelector(MetricIdentifier(Identifier))
),
Duration
)
),
At,
NumberLiteral
)
)
)
)
NumberLiteral,
FunctionCall(
FunctionIdentifier(Rate),
FunctionCallBody(
StepInvariantExpr(
MatrixSelector(VectorSelector(Identifier), Duration),
At,
NumberLiteral
)
)
)
)
)
)
)
)
)
# At modifier with negative number
@ -686,19 +465,13 @@ foo @ - 1234
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
NumberLiteral
)
)
)
# At modifier with explicit positive number
@ -707,19 +480,13 @@ foo @ + 1234
==>
PromQL(
Expr(
StepInvariantExpr(
Expr(
VectorSelector(
MetricIdentifier(
Identifier
)
)
)
),
At,
NumberLiteral
)
)
)
# Metric prefixed by Inf
@ -727,123 +494,123 @@ PromQL(
infra
==>
PromQL(Expr(VectorSelector(MetricIdentifier(Identifier))))
PromQL(VectorSelector(Identifier))
# Metric prefixed by Nan
nananere
==>
PromQL(Expr(VectorSelector(MetricIdentifier(Identifier))))
PromQL(VectorSelector(Identifier))
# Mixed-case NaN.
NaN
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Lower-cased NaN.
nan
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Inf.
Inf
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Negative Inf.
-Inf
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Positive Inf.
+Inf
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Lower-cased Inf.
inf
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Upper-cased Inf.
INF
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Negative number literal.
-42
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Explicitly positive number literal.
+42
==>
PromQL(Expr(NumberLiteral))
PromQL(NumberLiteral)
# Trying to illegally use NaN as a metric name.
NaN{foo="bar"}
==>
PromQL(Expr(BinaryExpr(Expr(NumberLiteral),⚠,BinModifiers,Expr(VectorSelector(LabelMatchers(LabelMatchList(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral))))))))
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
# Trying to illegally use Inf as a metric name.
Inf{foo="bar"}
==>
PromQL(Expr(BinaryExpr(Expr(NumberLiteral),⚠,BinModifiers,Expr(VectorSelector(LabelMatchers(LabelMatchList(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral))))))))
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
# Negative offset
foo offset -5d
==>
PromQL(Expr(OffsetExpr(Expr(VectorSelector(MetricIdentifier(Identifier))), Offset, Sub, Duration)))
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Sub, Duration))
# Negative offset with space
foo offset - 5d
==>
PromQL(Expr(OffsetExpr(Expr(VectorSelector(MetricIdentifier(Identifier))), Offset, Sub, Duration)))
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Sub, Duration))
# Positive offset
foo offset 5d
==>
PromQL(Expr(OffsetExpr(Expr(VectorSelector(MetricIdentifier(Identifier))), Offset, Duration)))
PromQL(OffsetExpr(VectorSelector(Identifier), Offset, Duration))
# Parsing only metric names with alternative @top { "top": "MetricName" }
sum:my_metric_name:rate5m
==>
MetricName(MetricIdentifier(Identifier))
MetricName(Identifier)
# Testing Atan2 inherited precedence level
1 + foo atan2 bar
==>
PromQL(Expr(BinaryExpr(Expr(NumberLiteral),Add,BinModifiers,Expr(BinaryExpr(Expr(VectorSelector(MetricIdentifier(Identifier))),Atan2,BinModifiers,Expr(VectorSelector(MetricIdentifier(Identifier))))))))
PromQL(BinaryExpr(NumberLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier))))

16
web/ui/react-app/src/pages/graph/SeriesName.test.tsx

@ -1,5 +1,5 @@
import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';
import SeriesName from './SeriesName';
describe('SeriesName', () => {
@ -52,27 +52,27 @@ describe('SeriesName', () => {
{ name: '}', className: 'legend-label-brace' },
];
const testLabelContainerElement = (text:string, expectedText:string, container: ShallowWrapper) => {
const testLabelContainerElement = (text: string, expectedText: string, container: ShallowWrapper) => {
expect(text).toEqual(expectedText);
expect(container.prop('className')).toEqual('legend-label-container');
expect(container.childAt(0).prop('className')).toEqual('legend-label-name');
expect(container.childAt(2).prop('className')).toEqual('legend-label-value');
}
};
testCases.forEach((tc, i) => {
const child = seriesName.childAt(i);
const firstChildElement = child.childAt(0);
const firstChildElementClass = firstChildElement.prop('className')
const firstChildElementClass = firstChildElement.prop('className');
const text = child
.children()
.map((ch) => ch.text())
.join('');
switch (child.children().length) {
case 1:
switch(firstChildElementClass) {
switch (firstChildElementClass) {
case 'legend-label-container':
testLabelContainerElement(text, `${tc.name}="${tc.value}"`, firstChildElement)
break
testLabelContainerElement(text, `${tc.name}="${tc.value}"`, firstChildElement);
break;
default:
expect(text).toEqual(tc.name);
expect(child.prop('className')).toEqual(tc.className);
@ -80,7 +80,7 @@ describe('SeriesName', () => {
break;
case 2:
const container = child.childAt(1);
testLabelContainerElement(text, `, ${tc.name}="${tc.value}"`, container)
testLabelContainerElement(text, `, ${tc.name}="${tc.value}"`, container);
break;
default:
fail('incorrect number of children: ' + child.children().length);

2
web/ui/react-app/src/pages/graph/SeriesName.tsx

@ -11,7 +11,7 @@ const SeriesName: FC<SeriesNameProps> = ({ labels, format }) => {
const setClipboardMsg = useToastContext();
const toClipboard = (e: React.MouseEvent<HTMLSpanElement>) => {
let copyText = e.currentTarget.innerText || '';
const copyText = e.currentTarget.innerText || '';
navigator.clipboard
.writeText(copyText.trim())
.then(() => {

Loading…
Cancel
Save