From 334b89f3ee77bd9dedbf8e3c05aba60da33ec8f3 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 31 Dec 2019 08:56:57 +0100 Subject: [PATCH] Prometheus: Fix label value suggestion (#21294) * Prometheus: Fix label value suggestion - remove quotes from typeahead input to suggest correct label values - fix acceptance of partial label values * Disable mid-word suggestions * Fix test --- .../prometheus/language_provider.test.ts | 2 +- .../datasource/prometheus/language_provider.ts | 15 ++++++++++++--- .../datasource/prometheus/language_utils.test.ts | 9 ++++++++- .../datasource/prometheus/language_utils.ts | 10 ++++++++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/language_provider.test.ts b/public/app/plugins/datasource/prometheus/language_provider.test.ts index 30b3943ff5f..970cbec4dbe 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.test.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.test.ts @@ -301,7 +301,7 @@ describe('Language completion provider', () => { instance.lookupsDisabled = false; const value = Plain.deserialize('{job!=}'); const ed = new SlateEditor({ value }); - const valueWithSelection = ed.moveForward(8).value; + const valueWithSelection = ed.moveForward(6).value; const result = await instance.provideCompletionItems({ text: '!=', prefix: '', diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 89c614c9790..79e2619ca51 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -88,7 +88,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { cleanText(s: string) { const parts = s.split(PREFIX_DELIMITER_REGEX); const last = parts.pop(); - return last.trimLeft().replace(/"$/, ''); + return last + .trimLeft() + .replace(/"$/, '') + .replace(/^"/, ''); } get syntax() { @@ -297,8 +300,15 @@ export default class PromQlLanguageProvider extends LanguageProvider { labelKey, value, }: TypeaheadInput): Promise => { + const suggestions: CompletionItemGroup[] = []; const line = value.anchorBlock.getText(); const cursorOffset = value.selection.anchor.offset; + const nextChar = line[cursorOffset]; + const isValueContext = wrapperClasses.includes('attr-value'); + if (!nextChar.match(/["}]/)) { + // Don't suggest anything inside a value + return { suggestions }; + } // Get normalized selector let selector; @@ -313,7 +323,6 @@ export default class PromQlLanguageProvider extends LanguageProvider { const containsMetric = selector.includes('__name__='); const existingKeys = parsedSelector ? parsedSelector.labelKeys : []; - const suggestions: CompletionItemGroup[] = []; let labelValues; // Query labels for selector if (selector) { @@ -326,7 +335,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { } let context: string; - if ((text && text.match(/^!?=~?/)) || wrapperClasses.includes('attr-value')) { + if ((text && text.match(/^!?=~?/)) || isValueContext) { // Label values if (labelKey && labelValues[labelKey]) { context = 'context-label-values'; diff --git a/public/app/plugins/datasource/prometheus/language_utils.test.ts b/public/app/plugins/datasource/prometheus/language_utils.test.ts index 11d83f61efd..978b3033eb8 100644 --- a/public/app/plugins/datasource/prometheus/language_utils.test.ts +++ b/public/app/plugins/datasource/prometheus/language_utils.test.ts @@ -31,6 +31,13 @@ describe('parseSelector()', () => { parsed = parseSelector('{foo="bar",baz="}'); expect(parsed.selector).toBe('{foo="bar"}'); + + // Cursor in value area counts as incomplete + parsed = parseSelector('{foo="bar",baz=""}', 16); + expect(parsed.selector).toBe('{foo="bar"}'); + + parsed = parseSelector('{foo="bar",baz="4"}', 17); + expect(parsed.selector).toBe('{foo="bar"}'); }); it('throws if not inside a selector', () => { @@ -55,7 +62,7 @@ describe('parseSelector()', () => { parsed = parseSelector('bar{foo}', 4); expect(parsed.selector).toBe('{__name__="bar"}'); - parsed = parseSelector('baz{foo="bar"}', 12); + parsed = parseSelector('baz{foo="bar"}', 13); expect(parsed.selector).toBe('{__name__="baz",foo="bar"}'); parsed = parseSelector('bar:metric:1m{}', 14); diff --git a/public/app/plugins/datasource/prometheus/language_utils.ts b/public/app/plugins/datasource/prometheus/language_utils.ts index 535f86fc8bc..a22f2b5c018 100644 --- a/public/app/plugins/datasource/prometheus/language_utils.ts +++ b/public/app/plugins/datasource/prometheus/language_utils.ts @@ -79,8 +79,14 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any // Extract clean labels to form clean selector, incomplete labels are dropped const selector = query.slice(prefixOpen, suffixClose); const labels: { [key: string]: { value: string; operator: string } } = {}; - selector.replace(labelRegexp, (_, key, operator, value) => { - labels[key] = { value, operator }; + selector.replace(labelRegexp, (label, key, operator, value) => { + const labelOffset = query.indexOf(label); + const valueStart = labelOffset + key.length + operator.length + 1; + const valueEnd = labelOffset + key.length + operator.length + value.length - 1; + // Skip label if cursor is in value + if (cursorOffset < valueStart || cursorOffset > valueEnd) { + labels[key] = { value, operator }; + } return ''; });