mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-11 13:57:36 -08:00
UTF-8: updates UI parser to support UTF-8 characters (#13590)
Signed-off-by: Neeraj Gartia <neerajgartia211002@gmail.com>
This commit is contained in:
parent
f7e923c3bb
commit
99f9d32499
|
@ -251,6 +251,12 @@ describe('analyzeCompletion test', () => {
|
||||||
pos: 11, // cursor is between the bracket after the string myL
|
pos: 11, // cursor is between the bracket after the string myL
|
||||||
expectedContext: [{ kind: ContextKind.LabelName }],
|
expectedContext: [{ kind: ContextKind.LabelName }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'continue to autocomplete QuotedLabelName in aggregate modifier',
|
||||||
|
expr: 'sum by ("myL")',
|
||||||
|
pos: 12, // cursor is between the bracket after the string myL
|
||||||
|
expectedContext: [{ kind: ContextKind.LabelName }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'autocomplete labelName in a list',
|
title: 'autocomplete labelName in a list',
|
||||||
expr: 'sum by (myLabel1,)',
|
expr: 'sum by (myLabel1,)',
|
||||||
|
@ -263,6 +269,12 @@ describe('analyzeCompletion test', () => {
|
||||||
pos: 23, // cursor is between the bracket after the string myLab
|
pos: 23, // cursor is between the bracket after the string myLab
|
||||||
expectedContext: [{ kind: ContextKind.LabelName }],
|
expectedContext: [{ kind: ContextKind.LabelName }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'autocomplete labelName in a list 2',
|
||||||
|
expr: 'sum by ("myLabel1", "myLab")',
|
||||||
|
pos: 27, // cursor is between the bracket after the string myLab
|
||||||
|
expectedContext: [{ kind: ContextKind.LabelName }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'autocomplete labelName associated to a metric',
|
title: 'autocomplete labelName associated to a metric',
|
||||||
expr: 'metric_name{}',
|
expr: 'metric_name{}',
|
||||||
|
@ -299,6 +311,12 @@ describe('analyzeCompletion test', () => {
|
||||||
pos: 22, // cursor is between the bracket after the comma
|
pos: 22, // cursor is between the bracket after the comma
|
||||||
expectedContext: [{ kind: ContextKind.LabelName, metricName: '' }],
|
expectedContext: [{ kind: ContextKind.LabelName, metricName: '' }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'continue to autocomplete quoted labelName associated to a metric',
|
||||||
|
expr: '{"metric_"}',
|
||||||
|
pos: 10, // cursor is between the bracket after the string metric_
|
||||||
|
expectedContext: [{ kind: ContextKind.MetricName, metricName: 'metric_' }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'autocomplete the labelValue with metricName + labelName',
|
title: 'autocomplete the labelValue with metricName + labelName',
|
||||||
expr: 'metric_name{labelName=""}',
|
expr: 'metric_name{labelName=""}',
|
||||||
|
@ -342,6 +360,30 @@ describe('analyzeCompletion test', () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'autocomplete the labelValue with metricName + quoted labelName',
|
||||||
|
expr: 'metric_name{labelName="labelValue", "labelName"!=""}',
|
||||||
|
pos: 50, // cursor is between the quotes
|
||||||
|
expectedContext: [
|
||||||
|
{
|
||||||
|
kind: ContextKind.LabelValue,
|
||||||
|
metricName: 'metric_name',
|
||||||
|
labelName: 'labelName',
|
||||||
|
matchers: [
|
||||||
|
{
|
||||||
|
name: 'labelName',
|
||||||
|
type: Neq,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'labelName',
|
||||||
|
type: EqlSingle,
|
||||||
|
value: 'labelValue',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'autocomplete the labelValue associated to a labelName',
|
title: 'autocomplete the labelValue associated to a labelName',
|
||||||
expr: '{labelName=""}',
|
expr: '{labelName=""}',
|
||||||
|
@ -427,6 +469,12 @@ describe('analyzeCompletion test', () => {
|
||||||
pos: 22, // cursor is after '!'
|
pos: 22, // cursor is after '!'
|
||||||
expectedContext: [{ kind: ContextKind.MatchOp }],
|
expectedContext: [{ kind: ContextKind.MatchOp }],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'autocomplete matchOp 3',
|
||||||
|
expr: 'metric_name{"labelName"!}',
|
||||||
|
pos: 24, // cursor is after '!'
|
||||||
|
expectedContext: [{ kind: ContextKind.BinOp }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'autocomplete duration with offset',
|
title: 'autocomplete duration with offset',
|
||||||
expr: 'http_requests_total offset 5',
|
expr: 'http_requests_total offset 5',
|
||||||
|
|
|
@ -29,7 +29,6 @@ import {
|
||||||
GroupingLabels,
|
GroupingLabels,
|
||||||
Gte,
|
Gte,
|
||||||
Gtr,
|
Gtr,
|
||||||
LabelMatcher,
|
|
||||||
LabelMatchers,
|
LabelMatchers,
|
||||||
LabelName,
|
LabelName,
|
||||||
Lss,
|
Lss,
|
||||||
|
@ -52,6 +51,9 @@ import {
|
||||||
SubqueryExpr,
|
SubqueryExpr,
|
||||||
Unless,
|
Unless,
|
||||||
VectorSelector,
|
VectorSelector,
|
||||||
|
UnquotedLabelMatcher,
|
||||||
|
QuotedLabelMatcher,
|
||||||
|
QuotedLabelName,
|
||||||
} from '@prometheus-io/lezer-promql';
|
} from '@prometheus-io/lezer-promql';
|
||||||
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
import { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState } from '@codemirror/state';
|
||||||
|
@ -181,7 +183,10 @@ export function computeStartCompletePosition(node: SyntaxNode, pos: number): num
|
||||||
let start = node.from;
|
let start = node.from;
|
||||||
if (node.type.id === LabelMatchers || node.type.id === GroupingLabels) {
|
if (node.type.id === LabelMatchers || node.type.id === GroupingLabels) {
|
||||||
start = computeStartCompleteLabelPositionInLabelMatcherOrInGroupingLabel(node, pos);
|
start = computeStartCompleteLabelPositionInLabelMatcherOrInGroupingLabel(node, pos);
|
||||||
} else if (node.type.id === FunctionCallBody || (node.type.id === StringLiteral && node.parent?.type.id === LabelMatcher)) {
|
} else if (
|
||||||
|
node.type.id === FunctionCallBody ||
|
||||||
|
(node.type.id === StringLiteral && (node.parent?.type.id === UnquotedLabelMatcher || node.parent?.type.id === QuotedLabelMatcher))
|
||||||
|
) {
|
||||||
// When the cursor is between bracket, quote, we need to increment the starting position to avoid to consider the open bracket/ first string.
|
// When the cursor is between bracket, quote, we need to increment the starting position to avoid to consider the open bracket/ first string.
|
||||||
start++;
|
start++;
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -212,7 +217,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
|
||||||
result.push({ kind: ContextKind.Duration });
|
result.push({ kind: ContextKind.Duration });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (node.parent?.type.id === LabelMatcher) {
|
if (node.parent?.type.id === UnquotedLabelMatcher || node.parent?.type.id === QuotedLabelMatcher) {
|
||||||
// In this case the current token is not itself a valid match op yet:
|
// In this case the current token is not itself a valid match op yet:
|
||||||
// metric_name{labelName!}
|
// metric_name{labelName!}
|
||||||
result.push({ kind: ContextKind.MatchOp });
|
result.push({ kind: ContextKind.MatchOp });
|
||||||
|
@ -380,7 +385,7 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
|
||||||
// sum by (myL)
|
// sum by (myL)
|
||||||
// So we have to continue to autocomplete any kind of labelName
|
// So we have to continue to autocomplete any kind of labelName
|
||||||
result.push({ kind: ContextKind.LabelName });
|
result.push({ kind: ContextKind.LabelName });
|
||||||
} else if (node.parent?.type.id === LabelMatcher) {
|
} else if (node.parent?.type.id === UnquotedLabelMatcher) {
|
||||||
// In that case we are in the given situation:
|
// In that case we are in the given situation:
|
||||||
// metric_name{myL} or {myL}
|
// metric_name{myL} or {myL}
|
||||||
// so we have or to continue to autocomplete any kind of labelName or
|
// so we have or to continue to autocomplete any kind of labelName or
|
||||||
|
@ -389,9 +394,9 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case StringLiteral:
|
case StringLiteral:
|
||||||
if (node.parent?.type.id === LabelMatcher) {
|
if (node.parent?.type.id === UnquotedLabelMatcher || node.parent?.type.id === QuotedLabelMatcher) {
|
||||||
// In this case we are in the given situation:
|
// In this case we are in the given situation:
|
||||||
// metric_name{labelName=""}
|
// metric_name{labelName=""} or metric_name{"labelName"=""}
|
||||||
// So we can autocomplete the labelValue
|
// So we can autocomplete the labelValue
|
||||||
|
|
||||||
// Get the labelName.
|
// Get the labelName.
|
||||||
|
@ -399,18 +404,34 @@ export function analyzeCompletion(state: EditorState, node: SyntaxNode): Context
|
||||||
let labelName = '';
|
let labelName = '';
|
||||||
if (node.parent.firstChild?.type.id === LabelName) {
|
if (node.parent.firstChild?.type.id === LabelName) {
|
||||||
labelName = state.sliceDoc(node.parent.firstChild.from, node.parent.firstChild.to);
|
labelName = state.sliceDoc(node.parent.firstChild.from, node.parent.firstChild.to);
|
||||||
|
} else if (node.parent.firstChild?.type.id === QuotedLabelName) {
|
||||||
|
labelName = state.sliceDoc(node.parent.firstChild.from, node.parent.firstChild.to).slice(1, -1);
|
||||||
}
|
}
|
||||||
// then find the metricName if it exists
|
// then find the metricName if it exists
|
||||||
const metricName = getMetricNameInVectorSelector(node, state);
|
const metricName = getMetricNameInVectorSelector(node, state);
|
||||||
// finally get the full matcher available
|
// finally get the full matcher available
|
||||||
const matcherNode = walkBackward(node, LabelMatchers);
|
const matcherNode = walkBackward(node, LabelMatchers);
|
||||||
const labelMatchers = buildLabelMatchers(matcherNode ? matcherNode.getChildren(LabelMatcher) : [], state);
|
const labelMatcherOpts = [QuotedLabelName, QuotedLabelMatcher, UnquotedLabelMatcher];
|
||||||
|
let labelMatchers: Matcher[] = [];
|
||||||
|
for (const labelMatcherOpt of labelMatcherOpts) {
|
||||||
|
labelMatchers = labelMatchers.concat(buildLabelMatchers(matcherNode ? matcherNode.getChildren(labelMatcherOpt) : [], state));
|
||||||
|
}
|
||||||
result.push({
|
result.push({
|
||||||
kind: ContextKind.LabelValue,
|
kind: ContextKind.LabelValue,
|
||||||
metricName: metricName,
|
metricName: metricName,
|
||||||
labelName: labelName,
|
labelName: labelName,
|
||||||
matchers: labelMatchers,
|
matchers: labelMatchers,
|
||||||
});
|
});
|
||||||
|
} else if (node.parent?.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
|
||||||
|
result.push({ kind: ContextKind.LabelName });
|
||||||
|
} else if (node.parent?.parent?.type.id === LabelMatchers) {
|
||||||
|
// In that case we are in the given situation:
|
||||||
|
// {""} or {"metric_"}
|
||||||
|
// since this is for the QuotedMetricName we need to continue to autocomplete for the metric names
|
||||||
|
result.push({ kind: ContextKind.MetricName, metricName: state.sliceDoc(node.from, node.to).slice(1, -1) });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NumberLiteral:
|
case NumberLiteral:
|
||||||
|
|
|
@ -12,15 +12,50 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { SyntaxNode } from '@lezer/common';
|
import { SyntaxNode } from '@lezer/common';
|
||||||
import { EqlRegex, EqlSingle, LabelName, MatchOp, Neq, NeqRegex, StringLiteral } from '@prometheus-io/lezer-promql';
|
import {
|
||||||
|
EqlRegex,
|
||||||
|
EqlSingle,
|
||||||
|
LabelName,
|
||||||
|
MatchOp,
|
||||||
|
Neq,
|
||||||
|
NeqRegex,
|
||||||
|
StringLiteral,
|
||||||
|
UnquotedLabelMatcher,
|
||||||
|
QuotedLabelMatcher,
|
||||||
|
QuotedLabelName,
|
||||||
|
} from '@prometheus-io/lezer-promql';
|
||||||
import { EditorState } from '@codemirror/state';
|
import { EditorState } from '@codemirror/state';
|
||||||
import { Matcher } from '../types';
|
import { Matcher } from '../types';
|
||||||
|
|
||||||
function createMatcher(labelMatcher: SyntaxNode, state: EditorState): Matcher {
|
function createMatcher(labelMatcher: SyntaxNode, state: EditorState): Matcher {
|
||||||
const matcher = new Matcher(0, '', '');
|
const matcher = new Matcher(0, '', '');
|
||||||
const cursor = labelMatcher.cursor();
|
const cursor = labelMatcher.cursor();
|
||||||
|
switch (cursor.type.id) {
|
||||||
|
case QuotedLabelMatcher:
|
||||||
if (!cursor.next()) {
|
if (!cursor.next()) {
|
||||||
// weird case, that would mean the labelMatcher doesn't have any child.
|
// weird case, that would mean the QuotedLabelMatcher doesn't have any child.
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
switch (cursor.type.id) {
|
||||||
|
case QuotedLabelName:
|
||||||
|
matcher.name = state.sliceDoc(cursor.from, cursor.to).slice(1, -1);
|
||||||
|
break;
|
||||||
|
case MatchOp:
|
||||||
|
const ope = cursor.node.firstChild;
|
||||||
|
if (ope) {
|
||||||
|
matcher.type = ope.type.id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case StringLiteral:
|
||||||
|
matcher.value = state.sliceDoc(cursor.from, cursor.to).slice(1, -1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (cursor.nextSibling());
|
||||||
|
break;
|
||||||
|
case UnquotedLabelMatcher:
|
||||||
|
if (!cursor.next()) {
|
||||||
|
// weird case, that would mean the UnquotedLabelMatcher doesn't have any child.
|
||||||
return matcher;
|
return matcher;
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
@ -39,6 +74,13 @@ function createMatcher(labelMatcher: SyntaxNode, state: EditorState): Matcher {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while (cursor.nextSibling());
|
} while (cursor.nextSibling());
|
||||||
|
break;
|
||||||
|
case QuotedLabelName:
|
||||||
|
matcher.name = '__name__';
|
||||||
|
matcher.value = state.sliceDoc(cursor.from, cursor.to).slice(1, -1);
|
||||||
|
matcher.type = EqlSingle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
return matcher;
|
return matcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,11 @@ describe('promql operations', () => {
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
expectedDiag: [] as Diagnostic[],
|
expectedDiag: [] as Diagnostic[],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expr: 'foo and on(test,"blub") bar',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [] as Diagnostic[],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expr: 'foo and on() bar',
|
expr: 'foo and on() bar',
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
|
@ -214,6 +219,11 @@ describe('promql operations', () => {
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
expectedDiag: [] as Diagnostic[],
|
expectedDiag: [] as Diagnostic[],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expr: 'foo and ignoring(test,"blub") bar',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [] as Diagnostic[],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expr: 'foo and ignoring() bar',
|
expr: 'foo and ignoring() bar',
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
|
@ -229,6 +239,11 @@ describe('promql operations', () => {
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
expectedDiag: [] as Diagnostic[],
|
expectedDiag: [] as Diagnostic[],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expr: 'foo / on(test,blub) group_left("bar") bar',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [] as Diagnostic[],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
expr: 'foo / ignoring(test,blub) group_left(blub) bar',
|
expr: 'foo / ignoring(test,blub) group_left(blub) bar',
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
|
@ -825,6 +840,134 @@ describe('promql operations', () => {
|
||||||
expectedValueType: ValueType.vector,
|
expectedValueType: ValueType.vector,
|
||||||
expectedDiag: [],
|
expectedDiag: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// with metric name in the middle
|
||||||
|
expr: '{a="b","foo",c~="d"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo", a="bc"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"colon:in:the:middle"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"dot.in.the.middle"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"😀 in metric name"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// quotes with escape
|
||||||
|
expr: '{"this is \"foo\" metric"}', // eslint-disable-line
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo","colon:in:the:middle"="val"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo","dot.in.the.middle"="val"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo","😀 in label name"="val"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// quotes with escape
|
||||||
|
expr: '{"foo","this is \"bar\" label"="val"}', // eslint-disable-line
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: 'foo{"bar"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
message: 'metric name must not be set twice: foo or bar',
|
||||||
|
severity: 'error',
|
||||||
|
to: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo", __name__="bar"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
message: 'metric name must not be set twice: foo or bar',
|
||||||
|
severity: 'error',
|
||||||
|
to: 23,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo", "__name__"="bar"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
message: 'metric name must not be set twice: foo or bar',
|
||||||
|
severity: 'error',
|
||||||
|
to: 25,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"__name__"="foo", __name__="bar"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
message: 'metric name must not be set twice: foo or bar',
|
||||||
|
severity: 'error',
|
||||||
|
to: 34,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{"foo", "bar"}',
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
to: 14,
|
||||||
|
message: 'metric name must not be set twice: foo or bar',
|
||||||
|
severity: 'error',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: `{'foo\`metric':'bar'}`, // eslint-disable-line
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: '{`foo\"metric`=`bar`}', // eslint-disable-line
|
||||||
|
expectedValueType: ValueType.vector,
|
||||||
|
expectedDiag: [],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
testCases.forEach((value) => {
|
testCases.forEach((value) => {
|
||||||
const state = createEditorState(value.expr);
|
const state = createEditorState(value.expr);
|
||||||
|
|
|
@ -27,7 +27,6 @@ import {
|
||||||
Gte,
|
Gte,
|
||||||
Gtr,
|
Gtr,
|
||||||
Identifier,
|
Identifier,
|
||||||
LabelMatcher,
|
|
||||||
LabelMatchers,
|
LabelMatchers,
|
||||||
Lss,
|
Lss,
|
||||||
Lte,
|
Lte,
|
||||||
|
@ -36,11 +35,14 @@ import {
|
||||||
Or,
|
Or,
|
||||||
ParenExpr,
|
ParenExpr,
|
||||||
Quantile,
|
Quantile,
|
||||||
|
QuotedLabelMatcher,
|
||||||
|
QuotedLabelName,
|
||||||
StepInvariantExpr,
|
StepInvariantExpr,
|
||||||
SubqueryExpr,
|
SubqueryExpr,
|
||||||
Topk,
|
Topk,
|
||||||
UnaryExpr,
|
UnaryExpr,
|
||||||
Unless,
|
Unless,
|
||||||
|
UnquotedLabelMatcher,
|
||||||
VectorSelector,
|
VectorSelector,
|
||||||
} from '@prometheus-io/lezer-promql';
|
} from '@prometheus-io/lezer-promql';
|
||||||
import { containsAtLeastOneChild } from './path-finder';
|
import { containsAtLeastOneChild } from './path-finder';
|
||||||
|
@ -282,7 +284,11 @@ export class Parser {
|
||||||
|
|
||||||
private checkVectorSelector(node: SyntaxNode): void {
|
private checkVectorSelector(node: SyntaxNode): void {
|
||||||
const matchList = node.getChild(LabelMatchers);
|
const matchList = node.getChild(LabelMatchers);
|
||||||
const labelMatchers = buildLabelMatchers(matchList ? matchList.getChildren(LabelMatcher) : [], this.state);
|
const labelMatcherOpts = [QuotedLabelName, QuotedLabelMatcher, UnquotedLabelMatcher];
|
||||||
|
let labelMatchers: Matcher[] = [];
|
||||||
|
for (const labelMatcherOpt of labelMatcherOpts) {
|
||||||
|
labelMatchers = labelMatchers.concat(buildLabelMatchers(matchList ? matchList.getChildren(labelMatcherOpt) : [], this.state));
|
||||||
|
}
|
||||||
let vectorSelectorName = '';
|
let vectorSelectorName = '';
|
||||||
// VectorSelector ( Identifier )
|
// VectorSelector ( Identifier )
|
||||||
// https://github.com/promlabs/lezer-promql/blob/71e2f9fa5ae6f5c5547d5738966cd2512e6b99a8/src/promql.grammar#L200
|
// https://github.com/promlabs/lezer-promql/blob/71e2f9fa5ae6f5c5547d5738966cd2512e6b99a8/src/promql.grammar#L200
|
||||||
|
@ -301,6 +307,14 @@ export class Parser {
|
||||||
// adding the metric name as a Matcher to avoid a false positive for this kind of expression:
|
// adding the metric name as a Matcher to avoid a false positive for this kind of expression:
|
||||||
// foo{bare=''}
|
// foo{bare=''}
|
||||||
labelMatchers.push(new Matcher(EqlSingle, '__name__', vectorSelectorName));
|
labelMatchers.push(new Matcher(EqlSingle, '__name__', vectorSelectorName));
|
||||||
|
} else {
|
||||||
|
// In this case when metric name is not set outside the braces
|
||||||
|
// It is checking whether metric name is set twice like in :
|
||||||
|
// {__name__:"foo", "foo"}, {"foo", "bar"}
|
||||||
|
const labelMatchersMetricName = labelMatchers.filter((lm) => lm.name === '__name__');
|
||||||
|
if (labelMatchersMetricName.length > 1) {
|
||||||
|
this.addDiagnostic(node, `metric name must not be set twice: ${labelMatchersMetricName[0].value} or ${labelMatchersMetricName[1].value}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Vector selector must contain at least one non-empty matcher to prevent
|
// A Vector selector must contain at least one non-empty matcher to prevent
|
||||||
|
|
|
@ -97,7 +97,7 @@ binModifiers {
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupingLabels {
|
GroupingLabels {
|
||||||
"(" (LabelName ("," LabelName)* ","?)? ")"
|
"(" ((LabelName | QuotedLabelName) ("," (LabelName | QuotedLabelName))* ","?)? ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
|
@ -220,7 +220,7 @@ VectorSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelMatchers {
|
LabelMatchers {
|
||||||
"{" (LabelMatcher ("," LabelMatcher)* ","?)? "}"
|
"{" ((UnquotedLabelMatcher | QuotedLabelMatcher | QuotedLabelName)("," (UnquotedLabelMatcher | QuotedLabelMatcher | QuotedLabelName))* ","?)? "}"
|
||||||
}
|
}
|
||||||
|
|
||||||
MatchOp {
|
MatchOp {
|
||||||
|
@ -230,10 +230,18 @@ MatchOp {
|
||||||
NeqRegex
|
NeqRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelMatcher {
|
UnquotedLabelMatcher {
|
||||||
LabelName MatchOp StringLiteral
|
LabelName MatchOp StringLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuotedLabelMatcher {
|
||||||
|
QuotedLabelName MatchOp StringLiteral
|
||||||
|
}
|
||||||
|
|
||||||
|
QuotedLabelName {
|
||||||
|
StringLiteral
|
||||||
|
}
|
||||||
|
|
||||||
StepInvariantExpr {
|
StepInvariantExpr {
|
||||||
expr At ( NumberLiteral | AtModifierPreprocessors "(" ")" )
|
expr At ( NumberLiteral | AtModifierPreprocessors "(" ")" )
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,54 @@ PromQL(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Quoted label name in grouping labels
|
||||||
|
|
||||||
|
sum by("job", mode) (test_metric) / on("job") group_left sum by("job")(test_metric)
|
||||||
|
|
||||||
|
==>
|
||||||
|
|
||||||
|
PromQL(
|
||||||
|
BinaryExpr(
|
||||||
|
AggregateExpr(
|
||||||
|
AggregateOp(Sum),
|
||||||
|
AggregateModifier(
|
||||||
|
By,
|
||||||
|
GroupingLabels(
|
||||||
|
QuotedLabelName(StringLiteral),
|
||||||
|
LabelName
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FunctionCallBody(
|
||||||
|
VectorSelector(
|
||||||
|
Identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Div,
|
||||||
|
MatchingModifierClause(
|
||||||
|
On,
|
||||||
|
GroupingLabels(
|
||||||
|
QuotedLabelName(StringLiteral)
|
||||||
|
)
|
||||||
|
GroupLeft
|
||||||
|
),
|
||||||
|
AggregateExpr(
|
||||||
|
AggregateOp(Sum),
|
||||||
|
AggregateModifier(
|
||||||
|
By,
|
||||||
|
GroupingLabels(
|
||||||
|
QuotedLabelName(StringLiteral)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
FunctionCallBody(
|
||||||
|
VectorSelector(
|
||||||
|
Identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Case insensitivity for aggregations and binop modifiers.
|
# Case insensitivity for aggregations and binop modifiers.
|
||||||
|
|
||||||
SuM BY(testlabel1) (testmetric1) / IGNOring(testlabel2) AVG withOUT(testlabel3) (testmetric2)
|
SuM BY(testlabel1) (testmetric1) / IGNOring(testlabel2) AVG withOUT(testlabel3) (testmetric2)
|
||||||
|
@ -226,22 +274,22 @@ PromQL(
|
||||||
VectorSelector(
|
VectorSelector(
|
||||||
Identifier,
|
Identifier,
|
||||||
LabelMatchers(
|
LabelMatchers(
|
||||||
LabelMatcher(
|
UnquotedLabelMatcher(
|
||||||
LabelName,
|
LabelName,
|
||||||
MatchOp(EqlSingle),
|
MatchOp(EqlSingle),
|
||||||
StringLiteral
|
StringLiteral
|
||||||
),
|
),
|
||||||
LabelMatcher(
|
UnquotedLabelMatcher(
|
||||||
LabelName,
|
LabelName,
|
||||||
MatchOp(Neq),
|
MatchOp(Neq),
|
||||||
StringLiteral
|
StringLiteral
|
||||||
),
|
),
|
||||||
LabelMatcher(
|
UnquotedLabelMatcher(
|
||||||
LabelName,
|
LabelName,
|
||||||
MatchOp(EqlRegex),
|
MatchOp(EqlRegex),
|
||||||
StringLiteral
|
StringLiteral
|
||||||
),
|
),
|
||||||
LabelMatcher(
|
UnquotedLabelMatcher(
|
||||||
LabelName,
|
LabelName,
|
||||||
MatchOp(NeqRegex),
|
MatchOp(NeqRegex),
|
||||||
StringLiteral
|
StringLiteral
|
||||||
|
@ -571,14 +619,14 @@ PromQL(NumberLiteral)
|
||||||
NaN{foo="bar"}
|
NaN{foo="bar"}
|
||||||
|
|
||||||
==>
|
==>
|
||||||
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
|
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
|
||||||
|
|
||||||
# Trying to illegally use Inf as a metric name.
|
# Trying to illegally use Inf as a metric name.
|
||||||
|
|
||||||
Inf{foo="bar"}
|
Inf{foo="bar"}
|
||||||
|
|
||||||
==>
|
==>
|
||||||
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(LabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
|
PromQL(BinaryExpr(NumberLiteral,⚠,VectorSelector(LabelMatchers(UnquotedLabelMatcher(LabelName,MatchOp(EqlSingle),StringLiteral)))))
|
||||||
|
|
||||||
# Negative offset
|
# Negative offset
|
||||||
|
|
||||||
|
@ -614,3 +662,24 @@ MetricName(Identifier)
|
||||||
|
|
||||||
==>
|
==>
|
||||||
PromQL(BinaryExpr(NumberLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier))))
|
PromQL(BinaryExpr(NumberLiteral,Add,BinaryExpr(VectorSelector(Identifier),Atan2,VectorSelector(Identifier))))
|
||||||
|
|
||||||
|
# Testing quoted metric name
|
||||||
|
|
||||||
|
{"metric_name"}
|
||||||
|
|
||||||
|
==>
|
||||||
|
PromQL(VectorSelector(LabelMatchers(QuotedLabelName(StringLiteral))))
|
||||||
|
|
||||||
|
# Testing quoted label name
|
||||||
|
|
||||||
|
{"foo"="bar"}
|
||||||
|
|
||||||
|
==>
|
||||||
|
PromQL(VectorSelector(LabelMatchers(QuotedLabelMatcher(QuotedLabelName(StringLiteral), MatchOp(EqlSingle), StringLiteral))))
|
||||||
|
|
||||||
|
# Testing quoted metric name and label name
|
||||||
|
|
||||||
|
{"metric_name", "foo"="bar"}
|
||||||
|
|
||||||
|
==>
|
||||||
|
PromQL(VectorSelector(LabelMatchers(QuotedLabelName(StringLiteral), QuotedLabelMatcher(QuotedLabelName(StringLiteral), MatchOp(EqlSingle), StringLiteral))))
|
Loading…
Reference in a new issue