From a61c8d23d4b1b393d869a667d0a4652b4c32c1f6 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt <david.kaltschmidt@gmail.com> Date: Fri, 30 Nov 2018 15:13:53 +0100 Subject: [PATCH] Explore: Fix label and history suggestions - fork promql's tokenizer (need to specify that labels context can only follow beginning of line or whitespace) - remove unneeded syntax features - only present history items when field is empty --- .../logging/language_provider.test.ts | 31 +++++++++++++++++-- .../datasource/logging/language_provider.ts | 19 +++++------- .../app/plugins/datasource/logging/syntax.ts | 28 +++++++++++++++++ 3 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 public/app/plugins/datasource/logging/syntax.ts diff --git a/public/app/plugins/datasource/logging/language_provider.test.ts b/public/app/plugins/datasource/logging/language_provider.test.ts index f4fc7efa7cb..8c2c805940f 100644 --- a/public/app/plugins/datasource/logging/language_provider.test.ts +++ b/public/app/plugins/datasource/logging/language_provider.test.ts @@ -8,9 +8,10 @@ describe('Language completion provider', () => { }; describe('empty query suggestions', () => { - it('returns default suggestions on emtpty context', () => { + it('returns no suggestions on emtpty context', () => { const instance = new LanguageProvider(datasource); - const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] }); + const value = Plain.deserialize(''); + const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }); expect(result.context).toBeUndefined(); expect(result.refresher).toBeUndefined(); expect(result.suggestions.length).toEqual(0); @@ -38,6 +39,32 @@ describe('Language completion provider', () => { }, ]); }); + + it('returns no suggestions within regexp', () => { + const instance = new LanguageProvider(datasource); + const value = Plain.deserialize('{} ()'); + const range = value.selection.merge({ + anchorOffset: 4, + }); + const valueWithSelection = value.change().select(range).value; + const history = [ + { + query: { refId: '1', expr: '{app="foo"}' }, + }, + ]; + const result = instance.provideCompletionItems( + { + text: '', + prefix: '', + value: valueWithSelection, + wrapperClasses: [], + }, + { history } + ); + expect(result.context).toBeUndefined(); + expect(result.refresher).toBeUndefined(); + expect(result.suggestions.length).toEqual(0); + }); }); describe('label suggestions', () => { diff --git a/public/app/plugins/datasource/logging/language_provider.ts b/public/app/plugins/datasource/logging/language_provider.ts index 21d2846ac63..a992084159a 100644 --- a/public/app/plugins/datasource/logging/language_provider.ts +++ b/public/app/plugins/datasource/logging/language_provider.ts @@ -10,7 +10,7 @@ import { HistoryItem, } from 'app/types/explore'; import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; -import PromqlSyntax from 'app/plugins/datasource/prometheus/promql'; +import syntax from './syntax'; import { DataQuery } from 'app/types'; const DEFAULT_KEYS = ['job', 'namespace']; @@ -55,7 +55,7 @@ export default class LoggingLanguageProvider extends LanguageProvider { cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); getSyntax() { - return PromqlSyntax; + return syntax; } request = url => { @@ -70,19 +70,14 @@ export default class LoggingLanguageProvider extends LanguageProvider { }; // Keep this DOM-free for testing - provideCompletionItems({ prefix, wrapperClasses, text }: TypeaheadInput, context?: any): TypeaheadOutput { - // Syntax spans have 3 classes by default. More indicate a recognized token - const tokenRecognized = wrapperClasses.length > 3; + provideCompletionItems({ prefix, wrapperClasses, text, value }: TypeaheadInput, context?: any): TypeaheadOutput { + // Local text properties + const empty = value.document.text.length === 0; // Determine candidates by CSS context if (_.includes(wrapperClasses, 'context-labels')) { - // Suggestions for metric{|} and metric{foo=|}, as well as metric-independent label queries like {|} + // Suggestions for {|} and {foo=|} return this.getLabelCompletionItems.apply(this, arguments); - } else if ( - // Show default suggestions in a couple of scenarios - (prefix && !tokenRecognized) || // Non-empty prefix, but not inside known token - (prefix === '' && !text.match(/^[\]})\s]+$/)) || // Empty prefix, but not following a closing brace - text.match(/[+\-*/^%]/) // Anything after binary operator - ) { + } else if (empty) { return this.getEmptyCompletionItems(context || {}); } diff --git a/public/app/plugins/datasource/logging/syntax.ts b/public/app/plugins/datasource/logging/syntax.ts new file mode 100644 index 00000000000..aca7d09ef4d --- /dev/null +++ b/public/app/plugins/datasource/logging/syntax.ts @@ -0,0 +1,28 @@ +/* tslint:disable max-line-length */ + +const tokenizer = { + comment: { + pattern: /(^|[^\n])#.*/, + lookbehind: true, + }, + 'context-labels': { + pattern: /(^|\s)\{[^}]*(?=})/, + lookbehind: true, + inside: { + 'label-key': { + pattern: /[a-z_]\w*(?=\s*(=|!=|=~|!~))/, + alias: 'attr-name', + }, + 'label-value': { + pattern: /"(?:\\.|[^\\"])*"/, + greedy: true, + alias: 'attr-value', + }, + }, + }, + // number: /\b-?\d+((\.\d*)?([eE][+-]?\d+)?)?\b/, + operator: new RegExp(`/&&?|\\|?\\||!=?|<(?:=>?|<|>)?|>[>=]?`, 'i'), + punctuation: /[{}`,.]/, +}; + +export default tokenizer;