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 719705d66af..53035523f29 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 @@ -2,6 +2,7 @@ import type { Situation, Label } from './situation'; import { NeverCaseError } from './util'; // FIXME: we should not load this from the "outside", but we cannot do that while we have the "old" query-field too import { FUNCTIONS } from '../../../promql'; +import { escapeLabelValueInExactSelector } from '../../../language_utils'; export type CompletionType = 'HISTORY' | 'FUNCTION' | 'METRIC_NAME' | 'DURATION' | 'LABEL_NAME' | 'LABEL_VALUE'; @@ -90,7 +91,9 @@ function makeSelector(metricName: string | undefined, labels: Label[]): string { allLabels.push({ name: '__name__', value: metricName, op: '=' }); } - const allLabelTexts = allLabels.map((label) => `${label.name}${label.op}"${label.value}"`); + const allLabelTexts = allLabels.map( + (label) => `${label.name}${label.op}"${escapeLabelValueInExactSelector(label.value)}"` + ); return `{${allLabelTexts.join(',')}}`; } diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.test.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.test.ts index 4be741b242b..8c7be6b592b 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.test.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.test.ts @@ -89,6 +89,24 @@ describe('situation', () => { type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME', otherLabels: [{ name: 'one', value: 'val1', op: '=' }], }); + + // single-quoted label-values with escape + assertSituation("{one='val\\'1',^}", { + type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME', + otherLabels: [{ name: 'one', value: "val'1", op: '=' }], + }); + + // double-quoted label-values with escape + assertSituation('{one="val\\"1",^}', { + type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME', + otherLabels: [{ name: 'one', value: 'val"1', op: '=' }], + }); + + // backticked label-values with escape (the escape should not be interpreted) + assertSituation('{one=`val\\"1`,^}', { + type: 'IN_LABEL_SELECTOR_NO_LABEL_NAME', + otherLabels: [{ name: 'one', value: 'val\\"1', op: '=' }], + }); }); it('handles label values', () => { diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts index f6a6ef53088..cef1009353f 100644 --- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts +++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/monaco-completion-provider/situation.ts @@ -63,17 +63,34 @@ function getNodeText(node: SyntaxNode, text: string): string { } function parsePromQLStringLiteral(text: string): string { + // if it is a string-literal, it is inside quotes of some kind + const inside = text.slice(1, text.length - 1); + // FIXME: support https://prometheus.io/docs/prometheus/latest/querying/basics/#string-literals // FIXME: maybe check other promql code, if all is supported or not + + // for now we do only some very simple un-escaping + // we start with double-quotes if (text.startsWith('"') && text.endsWith('"')) { - if (text.indexOf('\\') !== -1) { - throw new Error('FIXME: escape-sequences not supported in label-values'); - } - return text.slice(1, text.length - 1); - } else { - throw new Error('FIXME: invalid string literal'); + // NOTE: this is not 100% perfect, we only unescape the double-quote, + // there might be other characters too + return inside.replace(/\\"/, '"'); } + + // then single-quote + if (text.startsWith("'") && text.endsWith("'")) { + // NOTE: this is not 100% perfect, we only unescape the single-quote, + // there might be other characters too + return inside.replace(/\\'/, "'"); + } + + // then backticks + if (text.startsWith('`') && text.endsWith('`')) { + return inside; + } + + throw new Error('FIXME: invalid string literal'); } type LabelOperator = '=' | '!=' | '=~' | '!~';