From a8c5ab76b35b6921fd06aef9238a6f0551351885 Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Sun, 14 Oct 2018 10:43:48 +0800 Subject: [PATCH 1/2] Fix typeahead behaviour for QueryField These changes were originally intended to address a bug whereby a suggestion for an already selected label value continues to appear. However, they also appear to fix several other problems in the area: - Wrong suggestions when using negated label matching operators - Misaligned label value suggestion replacements Related: #13484 --- public/app/features/explore/PromQueryField.tsx | 12 ++++++------ public/app/features/explore/QueryField.tsx | 8 +++++++- public/app/features/explore/utils/prometheus.ts | 13 +++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/public/app/features/explore/PromQueryField.tsx b/public/app/features/explore/PromQueryField.tsx index 889666c5e35..442e51af987 100644 --- a/public/app/features/explore/PromQueryField.tsx +++ b/public/app/features/explore/PromQueryField.tsx @@ -111,7 +111,7 @@ export function willApplySuggestion( case 'context-label-values': { // Always add quotes and remove existing ones instead - if (!(typeaheadText.startsWith('="') || typeaheadText.startsWith('"'))) { + if (!typeaheadText.match(/^(!?=~?"|")/)) { suggestion = `"${suggestion}`; } if (getNextCharacter() !== '"') { @@ -421,7 +421,7 @@ class PromQueryField extends React.PureComponent -1; const existingKeys = parsedSelector ? parsedSelector.labelKeys : []; - if ((text && text.startsWith('=')) || _.includes(wrapperClasses, 'attr-value')) { + if ((text && text.match(/^!?=~?/)) || _.includes(wrapperClasses, 'attr-value')) { // Label values if (labelKey && this.state.labelValues[selector] && this.state.labelValues[selector][labelKey]) { const labelValues = this.state.labelValues[selector][labelKey]; @@ -571,10 +571,10 @@ class PromQueryField extends React.PureComponentLog labels ) : ( - - - - )} + + + + )}
diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index c89893b4f28..c3c41b7ab17 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -228,7 +228,13 @@ class QueryField extends React.PureComponent s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/; const selectorRegexp = /\{[^}]*?\}/; -const labelRegexp = /\b\w+="[^"\n]*?"/g; +const labelRegexp = /\b(\w+)(!?=~?)("[^"\n]*?")/g; export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any[]; selector: string } { if (!query.match(selectorRegexp)) { // Special matcher for metrics @@ -66,11 +66,8 @@ 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 = {}; - selector.replace(labelRegexp, match => { - const delimiterIndex = match.indexOf('='); - const key = match.slice(0, delimiterIndex); - const value = match.slice(delimiterIndex + 1, match.length); - labels[key] = value; + selector.replace(labelRegexp, (_, key, operator, value) => { + labels[key] = { value, operator }; return ''; }); @@ -78,12 +75,12 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any const metricPrefix = query.slice(0, prefixOpen); const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/); if (metricMatch) { - labels['__name__'] = `"${metricMatch[0]}"`; + labels['__name__'] = { value: `"${metricMatch[0]}"`, operator: '=' }; } // Build sorted selector const labelKeys = Object.keys(labels).sort(); - const cleanSelector = labelKeys.map(key => `${key}=${labels[key]}`).join(','); + const cleanSelector = labelKeys.map(key => `${key}${labels[key].operator}${labels[key].value}`).join(','); const selectorString = ['{', cleanSelector, '}'].join(''); From 22e0ff8b9ce404b5af1f99c54a44d3255ce08175 Mon Sep 17 00:00:00 2001 From: Michael Huynh Date: Sun, 14 Oct 2018 10:45:00 +0800 Subject: [PATCH 2/2] Update PromQueryField tests to address fixed bug Related: #13484 --- .../features/explore/PromQueryField.test.tsx | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/public/app/features/explore/PromQueryField.test.tsx b/public/app/features/explore/PromQueryField.test.tsx index c82a1cd448f..802bb695f2c 100644 --- a/public/app/features/explore/PromQueryField.test.tsx +++ b/public/app/features/explore/PromQueryField.test.tsx @@ -96,11 +96,14 @@ describe('PromQueryField typeahead handling', () => { it('returns label suggestions on label context but leaves out labels that already exist', () => { const instance = shallow( - + ).instance() as PromQueryField; - const value = Plain.deserialize('{job="foo",}'); + const value = Plain.deserialize('{job1="foo",job2!="foo",job3=~"foo",}'); const range = value.selection.merge({ - anchorOffset: 11, + anchorOffset: 36, }); const valueWithSelection = value.change().select(range).value; const result = instance.getTypeahead({ @@ -113,6 +116,33 @@ describe('PromQueryField typeahead handling', () => { expect(result.suggestions).toEqual([{ items: [{ label: 'bar' }], label: 'Labels' }]); }); + it('returns label value suggestions inside a label value context after a negated matching operator', () => { + const instance = shallow( + + ).instance() as PromQueryField; + const value = Plain.deserialize('{label!=}'); + const range = value.selection.merge({ anchorOffset: 8 }); + const valueWithSelection = value.change().select(range).value; + const result = instance.getTypeahead({ + text: '!=', + prefix: '', + wrapperClasses: ['context-labels'], + labelKey: 'label', + value: valueWithSelection, + }); + expect(result.context).toBe('context-label-values'); + expect(result.suggestions).toEqual([ + { + items: [{ label: 'a' }, { label: 'b' }, { label: 'c' }], + label: 'Label values for "label"', + }, + ]); + }); + it('returns a refresher on label context and unavailable metric', () => { const instance = shallow(