Commit 75923c43 by David Committed by GitHub

Promtheus: Improve tab completion (#20938)

Change in behavior:

- no longer suggest everything in empty field, only history
- term suggestions need at least one character
parent 67d83d1f
...@@ -13,35 +13,24 @@ describe('Language completion provider', () => { ...@@ -13,35 +13,24 @@ describe('Language completion provider', () => {
} as any) as PrometheusDatasource; } as any) as PrometheusDatasource;
describe('empty query suggestions', () => { describe('empty query suggestions', () => {
it('returns default suggestions on empty context', async () => { it('returns no suggestions on empty context', async () => {
const instance = new LanguageProvider(datasource); const instance = new LanguageProvider(datasource);
const value = Plain.deserialize(''); const value = Plain.deserialize('');
const result = await instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }); const result = await instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
expect(result.context).toBeUndefined(); expect(result.context).toBeUndefined();
expect(result.suggestions).toMatchObject([ expect(result.suggestions).toMatchObject([]);
{
label: 'Functions',
},
]);
}); });
it('returns default suggestions with metrics on empty context when metrics were provided', async () => { it('returns no suggestions with metrics on empty context even when metrics were provided', async () => {
const instance = new LanguageProvider(datasource); const instance = new LanguageProvider(datasource);
instance.metrics = ['foo', 'bar']; instance.metrics = ['foo', 'bar'];
const value = Plain.deserialize(''); const value = Plain.deserialize('');
const result = await instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] }); const result = await instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
expect(result.context).toBeUndefined(); expect(result.context).toBeUndefined();
expect(result.suggestions).toMatchObject([ expect(result.suggestions).toMatchObject([]);
{
label: 'Functions',
},
{
label: 'Metrics',
},
]);
}); });
it('returns default suggestions with history on empty context when history was provided', async () => { it('returns history on empty context when history was provided', async () => {
const instance = new LanguageProvider(datasource); const instance = new LanguageProvider(datasource);
const value = Plain.deserialize(''); const value = Plain.deserialize('');
const history: Array<HistoryItem<PromQuery>> = [ const history: Array<HistoryItem<PromQuery>> = [
...@@ -65,9 +54,6 @@ describe('Language completion provider', () => { ...@@ -65,9 +54,6 @@ describe('Language completion provider', () => {
}, },
], ],
}, },
{
label: 'Functions',
},
]); ]);
}); });
}); });
...@@ -101,15 +87,32 @@ describe('Language completion provider', () => { ...@@ -101,15 +87,32 @@ describe('Language completion provider', () => {
}); });
describe('metric suggestions', () => { describe('metric suggestions', () => {
it('returns metrics and function suggestions in an unknown context', async () => { it('returns history, metrics and function suggestions in an uknown context ', async () => {
const instance = new LanguageProvider(datasource); const instance = new LanguageProvider(datasource);
instance.metrics = ['foo', 'bar']; instance.metrics = ['foo', 'bar'];
const history: Array<HistoryItem<PromQuery>> = [
{
ts: 0,
query: { refId: '1', expr: 'metric' },
},
];
let value = Plain.deserialize('a'); let value = Plain.deserialize('a');
value = value.setSelection({ anchor: { offset: 1 }, focus: { offset: 1 } }); value = value.setSelection({ anchor: { offset: 1 }, focus: { offset: 1 } });
const result = await instance.provideCompletionItems({ text: 'a', prefix: 'a', value, wrapperClasses: [] }); const result = await instance.provideCompletionItems(
{ text: 'm', prefix: 'm', value, wrapperClasses: [] },
{ history }
);
expect(result.context).toBeUndefined(); expect(result.context).toBeUndefined();
expect(result.suggestions).toMatchObject([ expect(result.suggestions).toMatchObject([
{ {
label: 'History',
items: [
{
label: 'metric',
},
],
},
{
label: 'Functions', label: 'Functions',
}, },
{ {
...@@ -118,12 +121,28 @@ describe('Language completion provider', () => { ...@@ -118,12 +121,28 @@ describe('Language completion provider', () => {
]); ]);
}); });
it('returns metrics and function suggestions after a binary operator', async () => { it('returns no suggestions directly after a binary operator', async () => {
const instance = new LanguageProvider(datasource); const instance = new LanguageProvider(datasource);
instance.metrics = ['foo', 'bar']; instance.metrics = ['foo', 'bar'];
const value = Plain.deserialize('*'); const value = Plain.deserialize('*');
const result = await instance.provideCompletionItems({ text: '*', prefix: '', value, wrapperClasses: [] }); const result = await instance.provideCompletionItems({ text: '*', prefix: '', value, wrapperClasses: [] });
expect(result.context).toBeUndefined(); expect(result.context).toBeUndefined();
expect(result.suggestions).toMatchObject([]);
});
it('returns metric suggestions with prefix after a binary operator', async () => {
const instance = new LanguageProvider(datasource);
instance.metrics = ['foo', 'bar'];
const value = Plain.deserialize('foo + b');
const ed = new SlateEditor({ value });
const valueWithSelection = ed.moveForward(7).value;
const result = await instance.provideCompletionItems({
text: 'foo + b',
prefix: 'b',
value: valueWithSelection,
wrapperClasses: [],
});
expect(result.context).toBeUndefined();
expect(result.suggestions).toMatchObject([ expect(result.suggestions).toMatchObject([
{ {
label: 'Functions', label: 'Functions',
......
...@@ -72,7 +72,14 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -72,7 +72,14 @@ export default class PromQlLanguageProvider extends LanguageProvider {
} }
// Strip syntax chars // Strip syntax chars
cleanText = (s: string) => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim(); cleanText = (s: string) =>
s
.replace(/[{}[\]="(),!]/g, '')
.replace(/^\s*[~+\-*/^%]/, '')
.trim()
.split(' ')
.pop()
.trim();
get syntax() { get syntax() {
return PromqlSyntax; return PromqlSyntax;
...@@ -126,7 +133,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -126,7 +133,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
const noSuffix = !nextCharacter || nextCharacter === ')'; const noSuffix = !nextCharacter || nextCharacter === ')';
// Empty prefix is safe if it does not immediately follow a complete expression and has no text after it // Empty prefix is safe if it does not immediately follow a complete expression and has no text after it
const safeEmptyPrefix = prefix === '' && !text.match(/^[\]})\s]+$/) && noSuffix; const safePrefix = prefix && !text.match(/^[\]})\s]+$/) && noSuffix;
// About to type next operand if preceded by binary operator // About to type next operand if preceded by binary operator
const operatorsPattern = /[+\-*/^%]/; const operatorsPattern = /[+\-*/^%]/;
...@@ -145,7 +152,10 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -145,7 +152,10 @@ export default class PromQlLanguageProvider extends LanguageProvider {
} else if (empty) { } else if (empty) {
// Suggestions for empty query field // Suggestions for empty query field
return this.getEmptyCompletionItems(context); return this.getEmptyCompletionItems(context);
} else if ((prefixUnrecognized && noSuffix) || safeEmptyPrefix || isNextOperand) { } else if (prefixUnrecognized && noSuffix && !isNextOperand) {
// Show term suggestions in a couple of scenarios
return this.getBeginningCompletionItems(context);
} else if (prefixUnrecognized && safePrefix) {
// Show term suggestions in a couple of scenarios // Show term suggestions in a couple of scenarios
return this.getTermCompletionItems(); return this.getTermCompletionItems();
} }
...@@ -155,6 +165,12 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -155,6 +165,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
}; };
}; };
getBeginningCompletionItems = (context: { history: Array<HistoryItem<PromQuery>> }): TypeaheadOutput => {
return {
suggestions: [...this.getEmptyCompletionItems(context).suggestions, ...this.getTermCompletionItems().suggestions],
};
};
getEmptyCompletionItems = (context: { history: Array<HistoryItem<PromQuery>> }): TypeaheadOutput => { getEmptyCompletionItems = (context: { history: Array<HistoryItem<PromQuery>> }): TypeaheadOutput => {
const { history } = context; const { history } = context;
const suggestions = []; const suggestions = [];
...@@ -177,9 +193,6 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...@@ -177,9 +193,6 @@ export default class PromQlLanguageProvider extends LanguageProvider {
}); });
} }
const termCompletionItems = this.getTermCompletionItems();
suggestions.push(...termCompletionItems.suggestions);
return { suggestions }; return { suggestions };
}; };
......
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