mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
@@ -12,6 +12,56 @@ describe('Language completion provider', () => {
|
|||||||
getTimeRange: () => ({ start: 0, end: 1 }),
|
getTimeRange: () => ({ start: 0, end: 1 }),
|
||||||
} as any) as PrometheusDatasource;
|
} 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', () => {
|
describe('empty query suggestions', () => {
|
||||||
it('returns no suggestions on empty context', async () => {
|
it('returns no suggestions on empty context', async () => {
|
||||||
const instance = new LanguageProvider(datasource);
|
const instance = new LanguageProvider(datasource);
|
||||||
@@ -96,8 +146,9 @@ describe('Language completion provider', () => {
|
|||||||
query: { refId: '1', expr: 'metric' },
|
query: { refId: '1', expr: 'metric' },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let value = Plain.deserialize('a');
|
let value = Plain.deserialize('m');
|
||||||
value = value.setSelection({ anchor: { offset: 1 }, focus: { offset: 1 } });
|
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(
|
const result = await instance.provideCompletionItems(
|
||||||
{ text: 'm', prefix: 'm', value, wrapperClasses: [] },
|
{ text: 'm', prefix: 'm', value, wrapperClasses: [] },
|
||||||
{ history }
|
{ history }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import LRU from 'lru-cache';
|
import LRU from 'lru-cache';
|
||||||
|
import { Value } from 'slate';
|
||||||
|
|
||||||
import { dateTime, LanguageProvider, HistoryItem } from '@grafana/data';
|
import { dateTime, LanguageProvider, HistoryItem } from '@grafana/data';
|
||||||
import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui';
|
import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui';
|
||||||
@@ -50,6 +51,8 @@ function addMetricsMetadata(metric: string, metadata?: PromMetricsMetadata): Com
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PREFIX_DELIMITER_REGEX = /(="|!="|=~"|!~"|\{|\[|\(|\+|-|\/|\*|%|\^|and|or|unless|==|>=|!=|<=|>|<|=|~|,)/;
|
||||||
|
|
||||||
export default class PromQlLanguageProvider extends LanguageProvider {
|
export default class PromQlLanguageProvider extends LanguageProvider {
|
||||||
histogramMetrics?: string[];
|
histogramMetrics?: string[];
|
||||||
timeRange?: { start: number; end: number };
|
timeRange?: { start: number; end: number };
|
||||||
@@ -81,15 +84,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
Object.assign(this, initialValues);
|
Object.assign(this, initialValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip syntax chars
|
// Strip syntax chars so that typeahead suggestions can work on clean inputs
|
||||||
cleanText = (s: string) =>
|
cleanText(s: string) {
|
||||||
s
|
const parts = s.split(PREFIX_DELIMITER_REGEX);
|
||||||
.replace(/[{}[\]="(),!]/g, '')
|
const last = parts.pop();
|
||||||
.replace(/^\s*[~+\-*/^%]/, '')
|
return last.trimLeft().replace(/"$/, '');
|
||||||
.trim()
|
}
|
||||||
.split(' ')
|
|
||||||
.pop()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
get syntax() {
|
get syntax() {
|
||||||
return PromqlSyntax;
|
return PromqlSyntax;
|
||||||
@@ -159,7 +159,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
return this.getLabelCompletionItems({ prefix, text, value, labelKey, wrapperClasses });
|
return this.getLabelCompletionItems({ prefix, text, value, labelKey, wrapperClasses });
|
||||||
} else if (wrapperClasses.includes('context-aggregation')) {
|
} else if (wrapperClasses.includes('context-aggregation')) {
|
||||||
// Suggestions for sum(metric) by (|)
|
// Suggestions for sum(metric) by (|)
|
||||||
return this.getAggregationCompletionItems({ prefix, text, value, labelKey, wrapperClasses });
|
return this.getAggregationCompletionItems(value);
|
||||||
} else if (empty) {
|
} else if (empty) {
|
||||||
// Suggestions for empty query field
|
// Suggestions for empty query field
|
||||||
return this.getEmptyCompletionItems(context);
|
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[] = [];
|
const suggestions: CompletionItemGroup[] = [];
|
||||||
|
|
||||||
// Stitch all query lines together to support multi-line queries
|
// Stitch all query lines together to support multi-line queries
|
||||||
|
|||||||
Reference in New Issue
Block a user