prometheus: autocomplete: handle label-ops better (#41004)

This commit is contained in:
Gábor Farkas 2021-10-28 10:39:26 +02:00 committed by GitHub
parent eabaa3af2d
commit 25fe5bc027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 17 deletions

View File

@ -80,10 +80,10 @@ function makeSelector(metricName: string | undefined, labels: Label[]): string {
// we transform the metricName to a label, if it exists
if (metricName !== undefined) {
allLabels.push({ name: '__name__', value: metricName });
allLabels.push({ name: '__name__', value: metricName, op: '=' });
}
const allLabelTexts = allLabels.map((label) => `${label.name}="${label.value}"`);
const allLabelTexts = allLabels.map((label) => `${label.name}${label.op}"${label.value}"`);
return `{${allLabelTexts.join(',')}}`;
}

View File

@ -74,7 +74,7 @@ export function getCompletionProvider(
};
return {
triggerCharacters: ['{', ',', '[', '(', '='],
triggerCharacters: ['{', ',', '[', '(', '=', '~'],
provideCompletionItems,
};
}

View File

@ -67,12 +67,14 @@ describe('intent', () => {
otherLabels: [],
});
assertIntent('something{one="val1",two="val2",^}', {
assertIntent('something{one="val1",two!="val2",three=~"val3",four!~"val4",^}', {
type: 'LABEL_NAMES_FOR_SELECTOR',
metricName: 'something',
otherLabels: [
{ name: 'one', value: 'val1' },
{ name: 'two', value: 'val2' },
{ name: 'one', value: 'val1', op: '=' },
{ name: 'two', value: 'val2', op: '!=' },
{ name: 'three', value: 'val3', op: '=~' },
{ name: 'four', value: 'val4', op: '!~' },
],
});
@ -83,7 +85,7 @@ describe('intent', () => {
assertIntent('{one="val1",^}', {
type: 'LABEL_NAMES_FOR_SELECTOR',
otherLabels: [{ name: 'one', value: 'val1' }],
otherLabels: [{ name: 'one', value: 'val1', op: '=' }],
});
});
@ -95,28 +97,49 @@ describe('intent', () => {
otherLabels: [],
});
assertIntent('something{job!=^}', {
type: 'LABEL_VALUES',
metricName: 'something',
labelName: 'job',
otherLabels: [],
});
assertIntent('something{job=~^}', {
type: 'LABEL_VALUES',
metricName: 'something',
labelName: 'job',
otherLabels: [],
});
assertIntent('something{job!~^}', {
type: 'LABEL_VALUES',
metricName: 'something',
labelName: 'job',
otherLabels: [],
});
assertIntent('something{job=^,host="h1"}', {
type: 'LABEL_VALUES',
metricName: 'something',
labelName: 'job',
otherLabels: [{ name: 'host', value: 'h1' }],
otherLabels: [{ name: 'host', value: 'h1', op: '=' }],
});
assertIntent('{job=^,host="h1"}', {
type: 'LABEL_VALUES',
labelName: 'job',
otherLabels: [{ name: 'host', value: 'h1' }],
otherLabels: [{ name: 'host', value: 'h1', op: '=' }],
});
assertIntent('something{one="val1",two="val2",three=^,four="val4",five="val5"}', {
assertIntent('something{one="val1",two!="val2",three=^,four=~"val4",five!~"val5"}', {
type: 'LABEL_VALUES',
metricName: 'something',
labelName: 'three',
otherLabels: [
{ name: 'one', value: 'val1' },
{ name: 'two', value: 'val2' },
{ name: 'four', value: 'val4' },
{ name: 'five', value: 'val5' },
{ name: 'one', value: 'val1', op: '=' },
{ name: 'two', value: 'val2', op: '!=' },
{ name: 'four', value: 'val4', op: '=~' },
{ name: 'five', value: 'val5', op: '!~' },
],
});
});

View File

@ -2,7 +2,7 @@ import { parser } from 'lezer-promql';
import type { Tree, SyntaxNode } from 'lezer-tree';
import { NeverCaseError } from './util';
type Direction = 'parent' | 'firstChild' | 'lastChild';
type Direction = 'parent' | 'firstChild' | 'lastChild' | 'nextSibling';
type NodeTypeName =
| '⚠' // this is used as error-name
| 'AggregateExpr'
@ -18,7 +18,12 @@ type NodeTypeName =
| 'PromQL'
| 'StringLiteral'
| 'VectorSelector'
| 'MatrixSelector';
| 'MatrixSelector'
| 'MatchOp'
| 'EqlSingle'
| 'Neq'
| 'EqlRegex'
| 'NeqRegex';
type Path = Array<[Direction, NodeTypeName]>;
@ -30,6 +35,8 @@ function move(node: SyntaxNode, direction: Direction): SyntaxNode | null {
return node.firstChild;
case 'lastChild':
return node.lastChild;
case 'nextSibling':
return node.nextSibling;
default:
throw new NeverCaseError(direction);
}
@ -69,9 +76,12 @@ function parsePromQLStringLiteral(text: string): string {
}
}
type LabelOperator = '=' | '!=' | '=~' | '!~';
export type Label = {
name: string;
value: string;
op: LabelOperator;
};
export type Intent =
@ -142,6 +152,22 @@ const RESOLVERS: Resolver[] = [
},
];
const LABEL_OP_MAP = new Map<string, LabelOperator>([
['EqlSingle', '='],
['EqlRegex', '=~'],
['Neq', '!='],
['NeqRegex', '!~'],
]);
function getLabelOp(opNode: SyntaxNode): LabelOperator | null {
const opChild = opNode.firstChild;
if (opChild === null) {
return null;
}
return LABEL_OP_MAP.get(opChild.name) ?? null;
}
function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
if (labelMatcherNode.type.name !== 'LabelMatcher') {
return null;
@ -153,6 +179,16 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
return null;
}
const opNode = walk(nameNode, [['nextSibling', 'MatchOp']]);
if (opNode === null) {
return null;
}
const op = getLabelOp(opNode);
if (op === null) {
return null;
}
const valueNode = walk(labelMatcherNode, [['lastChild', 'StringLiteral']]);
if (valueNode === null) {
@ -162,7 +198,7 @@ function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null {
const name = getNodeText(nameNode, text);
const value = parsePromQLStringLiteral(getNodeText(valueNode, text));
return { name, value };
return { name, value, op };
}
function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] {
if (labelMatchersNode.type.name !== 'LabelMatchers') {