diff --git a/web/ui/module/codemirror-promql/src/complete/hybrid.ts b/web/ui/module/codemirror-promql/src/complete/hybrid.ts index e7286fca4..e3c02f890 100644 --- a/web/ui/module/codemirror-promql/src/complete/hybrid.ts +++ b/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 { diff --git a/web/ui/module/codemirror-promql/src/parser/index.ts b/web/ui/module/codemirror-promql/src/parser/index.ts index 2779e83c8..2de0d6255 100644 --- a/web/ui/module/codemirror-promql/src/parser/index.ts +++ b/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'; diff --git a/web/ui/module/codemirror-promql/src/parser/parser.ts b/web/ui/module/codemirror-promql/src/parser/parser.ts index bc9fcd035..58e56185c 100644 --- a/web/ui/module/codemirror-promql/src/parser/parser.ts +++ b/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); } diff --git a/web/ui/module/codemirror-promql/src/parser/path-finder.test.ts b/web/ui/module/codemirror-promql/src/parser/path-finder.test.ts index 514dddf10..82aaacfec 100644 --- a/web/ui/module/codemirror-promql/src/parser/path-finder.test.ts +++ b/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 = [ { diff --git a/web/ui/module/codemirror-promql/src/parser/path-finder.ts b/web/ui/module/codemirror-promql/src/parser/path-finder.ts index e93138b40..bc49dade5 100644 --- a/web/ui/module/codemirror-promql/src/parser/path-finder.ts +++ b/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; -} diff --git a/web/ui/module/codemirror-promql/src/parser/type.ts b/web/ui/module/codemirror-promql/src/parser/type.ts index 9bcb74843..4a4276da1 100644 --- a/web/ui/module/codemirror-promql/src/parser/type.ts +++ b/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; } diff --git a/web/ui/module/codemirror-promql/src/parser/vector.test.ts b/web/ui/module/codemirror-promql/src/parser/vector.test.ts index c4f2a8f03..f62820653 100644 --- a/web/ui/module/codemirror-promql/src/parser/vector.test.ts +++ b/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)); diff --git a/web/ui/module/codemirror-promql/src/parser/vector.ts b/web/ui/module/codemirror-promql/src/parser/vector.ts index c1a59dfb4..c47ca1fb7 100644 --- a/web/ui/module/codemirror-promql/src/parser/vector.ts +++ b/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)); } } } diff --git a/web/ui/module/lezer-promql/src/highlight.js b/web/ui/module/lezer-promql/src/highlight.js index 2e41c1c7e..c777a4eac 100644 --- a/web/ui/module/lezer-promql/src/highlight.js +++ b/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, diff --git a/web/ui/module/lezer-promql/src/promql.grammar b/web/ui/module/lezer-promql/src/promql.grammar index e4440ac85..c30c4fb0f 100644 --- a/web/ui/module/lezer-promql/src/promql.grammar +++ b/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 { diff --git a/web/ui/module/lezer-promql/test/expression.txt b/web/ui/module/lezer-promql/test/expression.txt index a4dfcd9ce..2e2b2f40b 100644 --- a/web/ui/module/lezer-promql/test/expression.txt +++ b/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)))))))) \ No newline at end of file +PromQL(BinaryExpr(NumberLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier)))) diff --git a/web/ui/react-app/src/pages/graph/SeriesName.test.tsx b/web/ui/react-app/src/pages/graph/SeriesName.test.tsx index d932fa7fd..bd271d5a9 100755 --- a/web/ui/react-app/src/pages/graph/SeriesName.test.tsx +++ b/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); diff --git a/web/ui/react-app/src/pages/graph/SeriesName.tsx b/web/ui/react-app/src/pages/graph/SeriesName.tsx index 9639653b3..59abfa59d 100644 --- a/web/ui/react-app/src/pages/graph/SeriesName.tsx +++ b/web/ui/react-app/src/pages/graph/SeriesName.tsx @@ -11,7 +11,7 @@ const SeriesName: FC = ({ labels, format }) => { const setClipboardMsg = useToastContext(); const toClipboard = (e: React.MouseEvent) => { - let copyText = e.currentTarget.innerText || ''; + const copyText = e.currentTarget.innerText || ''; navigator.clipboard .writeText(copyText.trim()) .then(() => {