Browse Source

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
Michael Huynh 7 years ago
parent
commit
a8c5ab76b3

+ 6 - 6
public/app/features/explore/PromQueryField.tsx

@@ -111,7 +111,7 @@ export function willApplySuggestion(
 
 
     case 'context-label-values': {
     case 'context-label-values': {
       // Always add quotes and remove existing ones instead
       // Always add quotes and remove existing ones instead
-      if (!(typeaheadText.startsWith('="') || typeaheadText.startsWith('"'))) {
+      if (!typeaheadText.match(/^(!?=~?"|")/)) {
         suggestion = `"${suggestion}`;
         suggestion = `"${suggestion}`;
       }
       }
       if (getNextCharacter() !== '"') {
       if (getNextCharacter() !== '"') {
@@ -421,7 +421,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
     const containsMetric = selector.indexOf('__name__=') > -1;
     const containsMetric = selector.indexOf('__name__=') > -1;
     const existingKeys = parsedSelector ? parsedSelector.labelKeys : [];
     const existingKeys = parsedSelector ? parsedSelector.labelKeys : [];
 
 
-    if ((text && text.startsWith('=')) || _.includes(wrapperClasses, 'attr-value')) {
+    if ((text && text.match(/^!?=~?/)) || _.includes(wrapperClasses, 'attr-value')) {
       // Label values
       // Label values
       if (labelKey && this.state.labelValues[selector] && this.state.labelValues[selector][labelKey]) {
       if (labelKey && this.state.labelValues[selector] && this.state.labelValues[selector][labelKey]) {
         const labelValues = this.state.labelValues[selector][labelKey];
         const labelValues = this.state.labelValues[selector][labelKey];
@@ -571,10 +571,10 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
               <button className="btn navbar-button navbar-button--tight">Log labels</button>
               <button className="btn navbar-button navbar-button--tight">Log labels</button>
             </Cascader>
             </Cascader>
           ) : (
           ) : (
-              <Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
-                <button className="btn navbar-button navbar-button--tight">Metrics</button>
-              </Cascader>
-            )}
+            <Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
+              <button className="btn navbar-button navbar-button--tight">Metrics</button>
+            </Cascader>
+          )}
         </div>
         </div>
         <div className="prom-query-field-wrapper">
         <div className="prom-query-field-wrapper">
           <div className="slate-query-field-wrapper">
           <div className="slate-query-field-wrapper">

+ 7 - 1
public/app/features/explore/QueryField.tsx

@@ -228,7 +228,13 @@ class QueryField extends React.PureComponent<TypeaheadFieldProps, TypeaheadField
       const offset = range.startOffset;
       const offset = range.startOffset;
       const text = selection.anchorNode.textContent;
       const text = selection.anchorNode.textContent;
       let prefix = text.substr(0, offset);
       let prefix = text.substr(0, offset);
-      if (cleanText) {
+
+      // Label values could have valid characters erased if `cleanText()` is
+      // blindly applied, which would undesirably interfere with suggestions
+      const labelValueMatch = prefix.match(/(?:!?=~?"?|")(.*)/);
+      if (labelValueMatch) {
+        prefix = labelValueMatch[1];
+      } else if (cleanText) {
         prefix = cleanText(prefix);
         prefix = cleanText(prefix);
       }
       }
 
 

+ 5 - 8
public/app/features/explore/utils/prometheus.ts

@@ -28,7 +28,7 @@ export const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
 
 
 // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/;
 // const cleanSelectorRegexp = /\{(\w+="[^"\n]*?")(,\w+="[^"\n]*?")*\}/;
 const selectorRegexp = /\{[^}]*?\}/;
 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 } {
 export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any[]; selector: string } {
   if (!query.match(selectorRegexp)) {
   if (!query.match(selectorRegexp)) {
     // Special matcher for metrics
     // 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
   // Extract clean labels to form clean selector, incomplete labels are dropped
   const selector = query.slice(prefixOpen, suffixClose);
   const selector = query.slice(prefixOpen, suffixClose);
   const labels = {};
   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 '';
     return '';
   });
   });
 
 
@@ -78,12 +75,12 @@ export function parseSelector(query: string, cursorOffset = 1): { labelKeys: any
   const metricPrefix = query.slice(0, prefixOpen);
   const metricPrefix = query.slice(0, prefixOpen);
   const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/);
   const metricMatch = metricPrefix.match(/[A-Za-z:][\w:]*$/);
   if (metricMatch) {
   if (metricMatch) {
-    labels['__name__'] = `"${metricMatch[0]}"`;
+    labels['__name__'] = { value: `"${metricMatch[0]}"`, operator: '=' };
   }
   }
 
 
   // Build sorted selector
   // Build sorted selector
   const labelKeys = Object.keys(labels).sort();
   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('');
   const selectorString = ['{', cleanSelector, '}'].join('');