mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
prometheus: autocomplete: handle label-ops better (#41004)
This commit is contained in:
parent
eabaa3af2d
commit
25fe5bc027
@ -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(',')}}`;
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ export function getCompletionProvider(
|
||||
};
|
||||
|
||||
return {
|
||||
triggerCharacters: ['{', ',', '[', '(', '='],
|
||||
triggerCharacters: ['{', ',', '[', '(', '=', '~'],
|
||||
provideCompletionItems,
|
||||
};
|
||||
}
|
||||
|
@ -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: '!~' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
@ -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') {
|
||||
|
Loading…
Reference in New Issue
Block a user