mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Don't suggest term items when text follows
Tab completion gets in the way when constructing a query from the inside out: ``` up| => |up => sum(|up) ``` At that point the language provider will not suggest anything.
This commit is contained in:
parent
46ebe245ab
commit
ff0ed06441
@ -78,9 +78,16 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Keep this DOM-free for testing
|
// Keep this DOM-free for testing
|
||||||
provideCompletionItems({ prefix, wrapperClasses, text }: TypeaheadInput, context?: any): TypeaheadOutput {
|
provideCompletionItems({ prefix, wrapperClasses, text, value }: TypeaheadInput, context?: any): TypeaheadOutput {
|
||||||
// Syntax spans have 3 classes by default. More indicate a recognized token
|
// Syntax spans have 3 classes by default. More indicate a recognized token
|
||||||
const tokenRecognized = wrapperClasses.length > 3;
|
const tokenRecognized = wrapperClasses.length > 3;
|
||||||
|
|
||||||
|
// Local text properties
|
||||||
|
const empty = value.document.text.length === 0;
|
||||||
|
const selectedLines = value.document.getTextsAtRangeAsArray(value.selection);
|
||||||
|
const currentLine = selectedLines.length === 1 ? selectedLines[0] : null;
|
||||||
|
const nextCharacter = currentLine ? currentLine.text[value.selection.anchorOffset] : null;
|
||||||
|
|
||||||
// Determine candidates by CSS context
|
// Determine candidates by CSS context
|
||||||
if (_.includes(wrapperClasses, 'context-range')) {
|
if (_.includes(wrapperClasses, 'context-range')) {
|
||||||
// Suggestions for metric[|]
|
// Suggestions for metric[|]
|
||||||
@ -90,13 +97,16 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
return this.getLabelCompletionItems.apply(this, arguments);
|
return this.getLabelCompletionItems.apply(this, arguments);
|
||||||
} else if (_.includes(wrapperClasses, 'context-aggregation')) {
|
} else if (_.includes(wrapperClasses, 'context-aggregation')) {
|
||||||
return this.getAggregationCompletionItems.apply(this, arguments);
|
return this.getAggregationCompletionItems.apply(this, arguments);
|
||||||
|
} else if (empty) {
|
||||||
|
return this.getEmptyCompletionItems(context || {});
|
||||||
} else if (
|
} else if (
|
||||||
// Show default suggestions in a couple of scenarios
|
// Show default suggestions in a couple of scenarios
|
||||||
(prefix && !tokenRecognized) || // Non-empty prefix, but not inside known token
|
(prefix && !tokenRecognized) || // Non-empty prefix, but not inside known token
|
||||||
(prefix === '' && !text.match(/^[\]})\s]+$/)) || // Empty prefix, but not following a closing brace
|
// Empty prefix, but not directly following a closing brace (e.g., `]|`), or not succeeded by anything except a closing parens, e.g., `sum(|)`
|
||||||
|
(prefix === '' && !text.match(/^[\]})\s]+$/) && (!nextCharacter || nextCharacter === ')')) ||
|
||||||
text.match(/[+\-*/^%]/) // Anything after binary operator
|
text.match(/[+\-*/^%]/) // Anything after binary operator
|
||||||
) {
|
) {
|
||||||
return this.getEmptyCompletionItems(context || {});
|
return this.getTermCompletionItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -106,8 +116,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
|
|
||||||
getEmptyCompletionItems(context: any): TypeaheadOutput {
|
getEmptyCompletionItems(context: any): TypeaheadOutput {
|
||||||
const { history } = context;
|
const { history } = context;
|
||||||
const { metrics } = this;
|
let suggestions: CompletionItemGroup[] = [];
|
||||||
const suggestions: CompletionItemGroup[] = [];
|
|
||||||
|
|
||||||
if (history && history.length > 0) {
|
if (history && history.length > 0) {
|
||||||
const historyItems = _.chain(history)
|
const historyItems = _.chain(history)
|
||||||
@ -126,13 +135,23 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const termCompletionItems = this.getTermCompletionItems();
|
||||||
|
suggestions = [...suggestions, ...termCompletionItems.suggestions];
|
||||||
|
|
||||||
|
return { suggestions };
|
||||||
|
}
|
||||||
|
|
||||||
|
getTermCompletionItems(): TypeaheadOutput {
|
||||||
|
const { metrics } = this;
|
||||||
|
const suggestions: CompletionItemGroup[] = [];
|
||||||
|
|
||||||
suggestions.push({
|
suggestions.push({
|
||||||
prefixMatch: true,
|
prefixMatch: true,
|
||||||
label: 'Functions',
|
label: 'Functions',
|
||||||
items: FUNCTIONS.map(setFunctionKind),
|
items: FUNCTIONS.map(setFunctionKind),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (metrics) {
|
if (metrics && metrics.length > 0) {
|
||||||
suggestions.push({
|
suggestions.push({
|
||||||
label: 'Metrics',
|
label: 'Metrics',
|
||||||
items: metrics.map(wrapLabel),
|
items: metrics.map(wrapLabel),
|
||||||
|
@ -7,18 +7,47 @@ describe('Language completion provider', () => {
|
|||||||
metadataRequest: () => ({ data: { data: [] } }),
|
metadataRequest: () => ({ data: { data: [] } }),
|
||||||
};
|
};
|
||||||
|
|
||||||
it('returns default suggestions on emtpty context', () => {
|
describe('empty query suggestions', () => {
|
||||||
const instance = new LanguageProvider(datasource);
|
it('returns default suggestions on emtpty context', () => {
|
||||||
const result = instance.provideCompletionItems({ text: '', prefix: '', wrapperClasses: [] });
|
const instance = new LanguageProvider(datasource);
|
||||||
expect(result.context).toBeUndefined();
|
const value = Plain.deserialize('');
|
||||||
expect(result.refresher).toBeUndefined();
|
const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
|
||||||
expect(result.suggestions.length).toEqual(2);
|
expect(result.context).toBeUndefined();
|
||||||
|
expect(result.refresher).toBeUndefined();
|
||||||
|
expect(result.suggestions).toMatchObject([
|
||||||
|
{
|
||||||
|
label: 'Functions',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns default suggestions with metrics on emtpty context when metrics were provided', () => {
|
||||||
|
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
||||||
|
const value = Plain.deserialize('');
|
||||||
|
const result = instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
|
||||||
|
expect(result.context).toBeUndefined();
|
||||||
|
expect(result.refresher).toBeUndefined();
|
||||||
|
expect(result.suggestions).toMatchObject([
|
||||||
|
{
|
||||||
|
label: 'Functions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Metrics',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('range suggestions', () => {
|
describe('range suggestions', () => {
|
||||||
it('returns range suggestions in range context', () => {
|
it('returns range suggestions in range context', () => {
|
||||||
const instance = new LanguageProvider(datasource);
|
const instance = new LanguageProvider(datasource);
|
||||||
const result = instance.provideCompletionItems({ text: '1', prefix: '1', wrapperClasses: ['context-range'] });
|
const value = Plain.deserialize('1');
|
||||||
|
const result = instance.provideCompletionItems({
|
||||||
|
text: '1',
|
||||||
|
prefix: '1',
|
||||||
|
value,
|
||||||
|
wrapperClasses: ['context-range'],
|
||||||
|
});
|
||||||
expect(result.context).toBe('context-range');
|
expect(result.context).toBe('context-range');
|
||||||
expect(result.refresher).toBeUndefined();
|
expect(result.refresher).toBeUndefined();
|
||||||
expect(result.suggestions).toEqual([
|
expect(result.suggestions).toEqual([
|
||||||
@ -31,20 +60,54 @@ describe('Language completion provider', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('metric suggestions', () => {
|
describe('metric suggestions', () => {
|
||||||
it('returns metrics suggestions by default', () => {
|
it('returns metrics and function suggestions in an unknown context', () => {
|
||||||
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
||||||
const result = instance.provideCompletionItems({ text: 'a', prefix: 'a', wrapperClasses: [] });
|
const value = Plain.deserialize('a');
|
||||||
|
const result = instance.provideCompletionItems({ text: 'a', prefix: 'a', value, wrapperClasses: [] });
|
||||||
expect(result.context).toBeUndefined();
|
expect(result.context).toBeUndefined();
|
||||||
expect(result.refresher).toBeUndefined();
|
expect(result.refresher).toBeUndefined();
|
||||||
expect(result.suggestions.length).toEqual(2);
|
expect(result.suggestions).toMatchObject([
|
||||||
|
{
|
||||||
|
label: 'Functions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Metrics',
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns default suggestions after a binary operator', () => {
|
it('returns metrics and function suggestions after a binary operator', () => {
|
||||||
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
||||||
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.context).toBeUndefined();
|
||||||
expect(result.refresher).toBeUndefined();
|
expect(result.refresher).toBeUndefined();
|
||||||
expect(result.suggestions.length).toEqual(2);
|
expect(result.suggestions).toMatchObject([
|
||||||
|
{
|
||||||
|
label: 'Functions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Metrics',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns no suggestions at the beginning of a non-empty function', () => {
|
||||||
|
const instance = new LanguageProvider(datasource, { metrics: ['foo', 'bar'] });
|
||||||
|
const value = Plain.deserialize('sum(up)');
|
||||||
|
const range = value.selection.merge({
|
||||||
|
anchorOffset: 4,
|
||||||
|
});
|
||||||
|
const valueWithSelection = value.change().select(range).value;
|
||||||
|
const result = instance.provideCompletionItems({
|
||||||
|
text: '',
|
||||||
|
prefix: '',
|
||||||
|
value: valueWithSelection,
|
||||||
|
wrapperClasses: [],
|
||||||
|
});
|
||||||
|
expect(result.context).toBeUndefined();
|
||||||
|
expect(result.refresher).toBeUndefined();
|
||||||
|
expect(result.suggestions.length).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user