diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/completions.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/completions.ts index eb3e2c56291..dc67d7c9d52 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/completions.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/completions.ts @@ -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(',')}}`; } diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/index.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/index.ts index eeb8a3e040d..3ef3f299bdb 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/index.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/index.ts @@ -74,7 +74,7 @@ export function getCompletionProvider( }; return { - triggerCharacters: ['{', ',', '[', '(', '='], + triggerCharacters: ['{', ',', '[', '(', '=', '~'], provideCompletionItems, }; } diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.test.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.test.ts index d07ffa97ba4..860fa4ad399 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.test.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.test.ts @@ -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: '!~' }, ], }); }); diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.ts index ff66cdbefff..4f1bf2f6293 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/intent.ts @@ -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([ + ['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') {