Commit 34c1433b by David Committed by GitHub

Prometheus: Fix typehead after binary operators (#21152)

- no longer do right-trim to prevent suggestions after space
- rely on splitting on syntax elements to identify relevant prefix
- added tests
parent ff422be9
......@@ -12,6 +12,56 @@ describe('Language completion provider', () => {
getTimeRange: () => ({ start: 0, end: 1 }),
} as any) as PrometheusDatasource;
describe('cleanText', () => {
const cleanText = new LanguageProvider(datasource).cleanText;
it('does not remove metric or label keys', () => {
expect(cleanText('foo')).toBe('foo');
expect(cleanText('foo_bar')).toBe('foo_bar');
});
it('keeps trailing space but removes leading', () => {
expect(cleanText('foo ')).toBe('foo ');
expect(cleanText(' foo')).toBe('foo');
});
it('removes label syntax', () => {
expect(cleanText('foo="bar')).toBe('bar');
expect(cleanText('foo!="bar')).toBe('bar');
expect(cleanText('foo=~"bar')).toBe('bar');
expect(cleanText('foo!~"bar')).toBe('bar');
expect(cleanText('{bar')).toBe('bar');
});
it('removes previous operators', () => {
expect(cleanText('foo + bar')).toBe('bar');
expect(cleanText('foo+bar')).toBe('bar');
expect(cleanText('foo - bar')).toBe('bar');
expect(cleanText('foo * bar')).toBe('bar');
expect(cleanText('foo / bar')).toBe('bar');
expect(cleanText('foo % bar')).toBe('bar');
expect(cleanText('foo ^ bar')).toBe('bar');
expect(cleanText('foo and bar')).toBe('bar');
expect(cleanText('foo or bar')).toBe('bar');
expect(cleanText('foo unless bar')).toBe('bar');
expect(cleanText('foo == bar')).toBe('bar');
expect(cleanText('foo != bar')).toBe('bar');
expect(cleanText('foo > bar')).toBe('bar');
expect(cleanText('foo < bar')).toBe('bar');
expect(cleanText('foo >= bar')).toBe('bar');
expect(cleanText('foo <= bar')).toBe('bar');
});
it('removes aggregation syntax', () => {
expect(cleanText('(bar')).toBe('bar');
expect(cleanText('(foo,bar')).toBe('bar');
expect(cleanText('(foo, bar')).toBe('bar');
});
it('removes range syntax', () => {
expect(cleanText('[1m')).toBe('1m');
});
});
describe('empty query suggestions', () => {
it('returns no suggestions on empty context', async () => {
const instance = new LanguageProvider(datasource);
......@@ -96,8 +146,9 @@ describe('Language completion provider', () => {
query: { refId: '1', expr: 'metric' },
},
];
let value = Plain.deserialize('a');
let value = Plain.deserialize('m');
value = value.setSelection({ anchor: { offset: 1 }, focus: { offset: 1 } });
// Even though no metric with `m` is present, we still get metric completion items, filtering is done by the consumer
const result = await instance.provideCompletionItems(
{ text: 'm', prefix: 'm', value, wrapperClasses: [] },
{ history }
......
import _ from 'lodash';
import LRU from 'lru-cache';
import { Value } from 'slate';
import { dateTime, LanguageProvider, HistoryItem } from '@grafana/data';
import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui';
......@@ -50,6 +51,8 @@ function addMetricsMetadata(metric: string, metadata?: PromMetricsMetadata): Com
return item;
}
const PREFIX_DELIMITER_REGEX = /(="|!="|=~"|!~"|\{|\[|\(|\+|-|\/|\*|%|\^|and|or|unless|==|>=|!=|<=|>|<|=|~|,)/;
export default class PromQlLanguageProvider extends LanguageProvider {
histogramMetrics?: string[];
timeRange?: { start: number; end: number };
......@@ -81,15 +84,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
Object.assign(this, initialValues);
}
// Strip syntax chars
cleanText = (s: string) =>
s
.replace(/[{}[\]="(),!]/g, '')
.replace(/^\s*[~+\-*/^%]/, '')
.trim()
.split(' ')
.pop()
.trim();
// Strip syntax chars so that typeahead suggestions can work on clean inputs
cleanText(s: string) {
const parts = s.split(PREFIX_DELIMITER_REGEX);
const last = parts.pop();
return last.trimLeft().replace(/"$/, '');
}
get syntax() {
return PromqlSyntax;
......@@ -159,7 +159,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
return this.getLabelCompletionItems({ prefix, text, value, labelKey, wrapperClasses });
} else if (wrapperClasses.includes('context-aggregation')) {
// Suggestions for sum(metric) by (|)
return this.getAggregationCompletionItems({ prefix, text, value, labelKey, wrapperClasses });
return this.getAggregationCompletionItems(value);
} else if (empty) {
// Suggestions for empty query field
return this.getEmptyCompletionItems(context);
......@@ -239,7 +239,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
};
}
getAggregationCompletionItems = async ({ value }: TypeaheadInput): Promise<TypeaheadOutput> => {
getAggregationCompletionItems = async (value: Value): Promise<TypeaheadOutput> => {
const suggestions: CompletionItemGroup[] = [];
// Stitch all query lines together to support multi-line queries
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment