apply same changes in core prometheus frontend
This commit is contained in:
@@ -2,6 +2,7 @@ import type { SyntaxNode, Tree } from '@lezer/common';
|
||||
import {
|
||||
AggregateExpr,
|
||||
AggregateModifier,
|
||||
BinaryExpr,
|
||||
EqlRegex,
|
||||
EqlSingle,
|
||||
FunctionCallBody,
|
||||
@@ -9,11 +10,9 @@ import {
|
||||
Identifier,
|
||||
LabelMatcher,
|
||||
LabelMatchers,
|
||||
LabelMatchList,
|
||||
LabelName,
|
||||
MatchOp,
|
||||
MatrixSelector,
|
||||
MetricIdentifier,
|
||||
Neq,
|
||||
NeqRegex,
|
||||
parser,
|
||||
@@ -35,9 +34,7 @@ type NodeTypeId =
|
||||
| typeof Identifier
|
||||
| typeof LabelMatcher
|
||||
| typeof LabelMatchers
|
||||
| typeof LabelMatchList
|
||||
| typeof LabelName
|
||||
| typeof MetricIdentifier
|
||||
| typeof PromQL
|
||||
| typeof StringLiteral
|
||||
| typeof VectorSelector
|
||||
@@ -183,6 +180,10 @@ const RESOLVERS: Resolver[] = [
|
||||
path: [StringLiteral, LabelMatcher],
|
||||
fun: resolveLabelMatcher,
|
||||
},
|
||||
{
|
||||
path: [ERROR_NODE_NAME, BinaryExpr, PromQL],
|
||||
fun: resolveTopLevel,
|
||||
},
|
||||
{
|
||||
path: [ERROR_NODE_NAME, LabelMatcher],
|
||||
fun: resolveLabelMatcher,
|
||||
@@ -251,30 +252,8 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
let listNode: SyntaxNode | null = walk(labelMatchersNode, [['firstChild', LabelMatchList]]);
|
||||
|
||||
const labels: Label[] = [];
|
||||
|
||||
while (listNode !== null) {
|
||||
const matcherNode = walk(listNode, [['lastChild', LabelMatcher]]);
|
||||
if (matcherNode === null) {
|
||||
// unexpected, we stop
|
||||
return [];
|
||||
}
|
||||
|
||||
const label = getLabel(matcherNode, text);
|
||||
if (label !== null) {
|
||||
labels.push(label);
|
||||
}
|
||||
|
||||
// there might be more labels
|
||||
listNode = walk(listNode, [['firstChild', LabelMatchList]]);
|
||||
}
|
||||
|
||||
// our labels-list is last-first, so we reverse it
|
||||
labels.reverse();
|
||||
|
||||
return labels;
|
||||
const labelNodes = labelMatchersNode.getChildren(LabelMatcher);
|
||||
return labelNodes.map((ln) => getLabel(ln, text)).filter(notEmpty);
|
||||
}
|
||||
|
||||
function getNodeChildren(node: SyntaxNode): SyntaxNode[] {
|
||||
@@ -318,17 +297,12 @@ function resolveLabelsForGrouping(node: SyntaxNode, text: string, pos: number):
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricIdNode = getNodeInSubtree(bodyNode, MetricIdentifier);
|
||||
const metricIdNode = getNodeInSubtree(bodyNode, Identifier);
|
||||
if (metricIdNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const idNode = walk(metricIdNode, [['firstChild', Identifier]]);
|
||||
if (idNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const metricName = getNodeText(idNode, text);
|
||||
const metricName = getNodeText(metricIdNode, text);
|
||||
return {
|
||||
type: 'IN_GROUPING',
|
||||
metricName,
|
||||
@@ -354,44 +328,11 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
|
||||
|
||||
const labelName = getNodeText(labelNameNode, text);
|
||||
|
||||
// now we need to go up, to the parent of LabelMatcher,
|
||||
// there can be one or many `LabelMatchList` parents, we have
|
||||
// to go through all of them
|
||||
|
||||
const firstListNode = walk(parent, [['parent', LabelMatchList]]);
|
||||
if (firstListNode === null) {
|
||||
const labelMatchersNode = walk(parent, [['parent', LabelMatchers]]);
|
||||
if (labelMatchersNode === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let listNode = firstListNode;
|
||||
|
||||
// we keep going through the parent-nodes
|
||||
// as long as they are LabelMatchList.
|
||||
// as soon as we reawch LabelMatchers, we stop
|
||||
let labelMatchersNode: SyntaxNode | null = null;
|
||||
while (labelMatchersNode === null) {
|
||||
const p = listNode.parent;
|
||||
if (p === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { id } = p.type;
|
||||
|
||||
switch (id) {
|
||||
case LabelMatchList:
|
||||
//we keep looping
|
||||
listNode = p;
|
||||
continue;
|
||||
case LabelMatchers:
|
||||
// we reached the end, we can stop the loop
|
||||
labelMatchersNode = p;
|
||||
continue;
|
||||
default:
|
||||
// we reached some other node, we stop
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// now we need to find the other names
|
||||
const allLabels = getLabels(labelMatchersNode, text);
|
||||
|
||||
@@ -400,7 +341,6 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa
|
||||
|
||||
const metricNameNode = walk(labelMatchersNode, [
|
||||
['parent', VectorSelector],
|
||||
['firstChild', MetricIdentifier],
|
||||
['firstChild', Identifier],
|
||||
]);
|
||||
|
||||
@@ -459,7 +399,7 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
|
||||
|
||||
// next false positive:
|
||||
// `something{a="1"^}`
|
||||
const child = walk(node, [['firstChild', LabelMatchList]]);
|
||||
const child = walk(node, [['firstChild', LabelMatcher]]);
|
||||
if (child !== null) {
|
||||
// means the label-matching part contains at least one label already.
|
||||
//
|
||||
@@ -476,7 +416,6 @@ function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number)
|
||||
|
||||
const metricNameNode = walk(node, [
|
||||
['parent', VectorSelector],
|
||||
['firstChild', MetricIdentifier],
|
||||
['firstChild', Identifier],
|
||||
]);
|
||||
|
||||
@@ -532,12 +471,12 @@ export function getSituation(text: string, pos: number): Situation | null {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
PromQL
|
||||
Expr
|
||||
VectorSelector
|
||||
LabelMatchers
|
||||
*/
|
||||
/**
|
||||
PromQL
|
||||
Expr
|
||||
VectorSelector
|
||||
LabelMatchers
|
||||
*/
|
||||
const tree = parser.parse(text);
|
||||
|
||||
// if the tree contains error, it is very probable that
|
||||
@@ -565,3 +504,7 @@ export function getSituation(text: string, pos: number): Situation | null {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
@@ -434,6 +434,12 @@ describe('buildVisualQueryFromString', () => {
|
||||
to: 27,
|
||||
parentType: 'VectorSelector',
|
||||
},
|
||||
{
|
||||
text: ')',
|
||||
from: 38,
|
||||
to: 39,
|
||||
parentType: 'PromQL',
|
||||
},
|
||||
],
|
||||
query: {
|
||||
metric: '${func_var}',
|
||||
@@ -687,7 +693,7 @@ describe('buildVisualQueryFromString', () => {
|
||||
errors: [
|
||||
{
|
||||
from: 6,
|
||||
parentType: 'Expr',
|
||||
parentType: 'BinaryExpr',
|
||||
text: '(bar + baz)',
|
||||
to: 17,
|
||||
},
|
||||
|
||||
@@ -4,22 +4,18 @@ import {
|
||||
AggregateModifier,
|
||||
AggregateOp,
|
||||
BinaryExpr,
|
||||
BinModifiers,
|
||||
Expr,
|
||||
BoolModifier,
|
||||
FunctionCall,
|
||||
FunctionCallArgs,
|
||||
FunctionCallBody,
|
||||
FunctionIdentifier,
|
||||
GroupingLabel,
|
||||
GroupingLabelList,
|
||||
GroupingLabels,
|
||||
Identifier,
|
||||
LabelMatcher,
|
||||
LabelName,
|
||||
MatchingModifierClause,
|
||||
MatchOp,
|
||||
MetricIdentifier,
|
||||
NumberLiteral,
|
||||
On,
|
||||
OnOrIgnoring,
|
||||
ParenExpr,
|
||||
parser,
|
||||
StringLiteral,
|
||||
@@ -101,6 +97,7 @@ interface Context {
|
||||
errors: ParsingError[];
|
||||
}
|
||||
|
||||
// TODO find a better approach for grafana global variables
|
||||
function isValidPromQLMinusGrafanaGlobalVariables(expr: string) {
|
||||
const context: Context = {
|
||||
query: {
|
||||
@@ -141,7 +138,7 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex
|
||||
const visQuery = context.query;
|
||||
|
||||
switch (node.type.id) {
|
||||
case MetricIdentifier: {
|
||||
case Identifier: {
|
||||
// Expectation is that there is only one of those per query.
|
||||
visQuery.metric = getString(expr, node);
|
||||
break;
|
||||
@@ -182,8 +179,8 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex
|
||||
|
||||
default: {
|
||||
if (node.type.id === ParenExpr) {
|
||||
// We don't support parenthesis in the query to group expressions. We just report error but go on with the
|
||||
// parsing.
|
||||
// We don't support parenthesis in the query to group expressions.
|
||||
// We just report error but go on with the parsing.
|
||||
context.errors.push(makeError(expr, node));
|
||||
}
|
||||
// Any other nodes we just ignore and go to its children. This should be fine as there are lots of wrapper
|
||||
@@ -199,8 +196,9 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex
|
||||
}
|
||||
}
|
||||
|
||||
// TODO check if we still need this
|
||||
function isIntervalVariableError(node: SyntaxNode) {
|
||||
return node.prevSibling?.type.id === Expr && node.prevSibling?.firstChild?.type.id === VectorSelector;
|
||||
return node.prevSibling?.firstChild?.type.id === VectorSelector;
|
||||
}
|
||||
|
||||
function getLabel(expr: string, node: SyntaxNode): QueryBuilderLabelFilter {
|
||||
@@ -228,7 +226,6 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) {
|
||||
const funcName = getString(expr, nameNode);
|
||||
|
||||
const body = node.getChild(FunctionCallBody);
|
||||
const callArgs = body!.getChild(FunctionCallArgs);
|
||||
const params = [];
|
||||
let interval = '';
|
||||
|
||||
@@ -248,13 +245,13 @@ function handleFunction(expr: string, node: SyntaxNode, context: Context) {
|
||||
// We unshift operations to keep the more natural order that we want to have in the visual query editor.
|
||||
visQuery.operations.unshift(op);
|
||||
|
||||
if (callArgs) {
|
||||
if (getString(expr, callArgs) === interval + ']') {
|
||||
if (body) {
|
||||
if (getString(expr, body) === '([' + interval + '])') {
|
||||
// This is a special case where we have a function with a single argument and it is the interval.
|
||||
// This happens when you start adding operations in query builder and did not set a metric yet.
|
||||
return;
|
||||
}
|
||||
updateFunctionArgs(expr, callArgs, context, op);
|
||||
updateFunctionArgs(expr, body, context, op);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,25 +280,14 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) {
|
||||
funcName = `__${funcName}_without`;
|
||||
}
|
||||
|
||||
labels.push(...getAllByType(expr, modifier, GroupingLabel));
|
||||
labels.push(...getAllByType(expr, modifier, LabelName));
|
||||
}
|
||||
|
||||
const body = node.getChild(FunctionCallBody);
|
||||
const callArgs = body!.getChild(FunctionCallArgs);
|
||||
const callArgsExprChild = callArgs?.getChild(Expr);
|
||||
const binaryExpressionWithinAggregationArgs = callArgsExprChild?.getChild(BinaryExpr);
|
||||
|
||||
if (binaryExpressionWithinAggregationArgs) {
|
||||
context.errors.push({
|
||||
text: 'Query parsing is ambiguous.',
|
||||
from: binaryExpressionWithinAggregationArgs.from,
|
||||
to: binaryExpressionWithinAggregationArgs.to,
|
||||
});
|
||||
}
|
||||
|
||||
const op: QueryBuilderOperation = { id: funcName, params: [] };
|
||||
visQuery.operations.unshift(op);
|
||||
updateFunctionArgs(expr, callArgs, context, op);
|
||||
updateFunctionArgs(expr, body, context, op);
|
||||
// We add labels after params in the visual query editor.
|
||||
op.params.push(...labels);
|
||||
}
|
||||
@@ -309,8 +295,7 @@ function handleAggregation(expr: string, node: SyntaxNode, context: Context) {
|
||||
/**
|
||||
* Handle (probably) all types of arguments that function or aggregation can have.
|
||||
*
|
||||
* FunctionCallArgs are nested bit weirdly basically its [firstArg, ...rest] where rest is again FunctionCallArgs so
|
||||
* we cannot just get all the children and iterate them as arguments we have to again recursively traverse through
|
||||
* We cannot just get all the children and iterate them as arguments we have to again recursively traverse through
|
||||
* them.
|
||||
*
|
||||
* @param expr
|
||||
@@ -323,15 +308,16 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont
|
||||
return;
|
||||
}
|
||||
switch (node.type.id) {
|
||||
// In case we have an expression we don't know what kind so we have to look at the child as it can be anything.
|
||||
case Expr:
|
||||
// FunctionCallArgs are nested bit weirdly as mentioned so we have to go one deeper in this case.
|
||||
case FunctionCallArgs: {
|
||||
case FunctionCallBody: {
|
||||
let child = node.firstChild;
|
||||
|
||||
while (child) {
|
||||
const callArgsExprChild = child.getChild(Expr);
|
||||
const binaryExpressionWithinFunctionArgs = callArgsExprChild?.getChild(BinaryExpr);
|
||||
let binaryExpressionWithinFunctionArgs: SyntaxNode | null;
|
||||
if (child?.type.id === BinaryExpr) {
|
||||
binaryExpressionWithinFunctionArgs = child;
|
||||
} else {
|
||||
binaryExpressionWithinFunctionArgs = child?.getChild(BinaryExpr);
|
||||
}
|
||||
|
||||
if (binaryExpressionWithinFunctionArgs) {
|
||||
context.errors.push({
|
||||
@@ -344,7 +330,6 @@ function updateFunctionArgs(expr: string, node: SyntaxNode | null, context: Cont
|
||||
updateFunctionArgs(expr, child, context, op);
|
||||
child = child.nextSibling;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -377,16 +362,16 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
const visQuery = context.query;
|
||||
const left = node.firstChild!;
|
||||
const op = getString(expr, left.nextSibling);
|
||||
const binModifier = getBinaryModifier(expr, node.getChild(BinModifiers));
|
||||
const binModifier = getBinaryModifier(expr, node.getChild(BoolModifier) ?? node.getChild(MatchingModifierClause));
|
||||
|
||||
const right = node.lastChild!;
|
||||
|
||||
const opDef = binaryScalarOperatorToOperatorName[op];
|
||||
|
||||
const leftNumber = left.getChild(NumberLiteral);
|
||||
const rightNumber = right.getChild(NumberLiteral);
|
||||
const leftNumber = left.type.id === NumberLiteral;
|
||||
const rightNumber = right.type.id === NumberLiteral;
|
||||
|
||||
const rightBinary = right.getChild(BinaryExpr);
|
||||
const rightBinary = right.type.id === BinaryExpr;
|
||||
|
||||
if (leftNumber) {
|
||||
// TODO: this should be already handled in case parent is binary expression as it has to be added to parent
|
||||
@@ -432,6 +417,7 @@ function handleBinary(expr: string, node: SyntaxNode, context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO revisit this function.
|
||||
function getBinaryModifier(
|
||||
expr: string,
|
||||
node: SyntaxNode | null
|
||||
@@ -439,26 +425,26 @@ function getBinaryModifier(
|
||||
| { isBool: true; isMatcher: false }
|
||||
| { isBool: false; isMatcher: true; matches: string; matchType: 'ignoring' | 'on' }
|
||||
| undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
if (node.getChild('Bool')) {
|
||||
return { isBool: true, isMatcher: false };
|
||||
} else {
|
||||
const matcher = node.getChild(OnOrIgnoring);
|
||||
if (!matcher) {
|
||||
// Not sure what this could be, maybe should be an error.
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
const labels = getString(expr, matcher.getChild(GroupingLabels)?.getChild(GroupingLabelList));
|
||||
return {
|
||||
isMatcher: true,
|
||||
isBool: false,
|
||||
matches: labels,
|
||||
matchType: matcher.getChild(On) ? 'on' : 'ignoring',
|
||||
};
|
||||
if (node.getChild('Bool')) {
|
||||
return { isBool: true, isMatcher: false };
|
||||
} else {
|
||||
let labels = '';
|
||||
const groupingLabels = node.getChild(GroupingLabels);
|
||||
if (groupingLabels) {
|
||||
labels = getAllByType(expr, groupingLabels, LabelName).join(', ');
|
||||
}
|
||||
|
||||
return {
|
||||
isMatcher: true,
|
||||
isBool: false,
|
||||
matches: labels,
|
||||
matchType: node.getChild(On) ? 'on' : 'ignoring',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isEmptyQuery(query: PromVisualQuery) {
|
||||
if (query.labels.length === 0 && query.operations.length === 0 && !query.metric) {
|
||||
|
||||
@@ -113,11 +113,10 @@ export function makeBinOp(
|
||||
* not be safe is it would also find arguments of nested functions.
|
||||
* @param expr
|
||||
* @param cur
|
||||
* @param type - can be string or number, some data-sources (loki) haven't migrated over to using numeric constants defined in the lezer parsing library (e.g. lezer-promql).
|
||||
* @todo Remove string type definition when all data-sources have migrated to numeric constants
|
||||
* @param type
|
||||
*/
|
||||
export function getAllByType(expr: string, cur: SyntaxNode, type: number | string): string[] {
|
||||
if (cur.type.id === type || cur.name === type) {
|
||||
export function getAllByType(expr: string, cur: SyntaxNode, type: number): string[] {
|
||||
if (cur.type.id === type) {
|
||||
return [getString(expr, cur)];
|
||||
}
|
||||
const values: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user