Commit 3667781e by David Committed by GitHub

Loki: fix filter expression suggestions (#21290)

* Loki: fix filter expression suggestions

- dont suggest term completion items in filter expression
- allow at least one character before suggesting term items
- keep logql expression when switching between Metrics/Logs mode
- show only history by default in completion items

* Clear results when changing mode
parent 334b89f3
......@@ -153,7 +153,6 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
*/
export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult<void> {
return dispatch => {
dispatch(clearQueriesAction({ exploreId }));
dispatch(changeModeAction({ exploreId, mode }));
};
}
......
......@@ -61,13 +61,17 @@ describe('Explore item reducer', () => {
});
describe('changing datasource', () => {
describe('when changeDataType is dispatched', () => {
describe('when changeMode is dispatched', () => {
it('then it should set correct state', () => {
reducerTester()
.givenReducer(itemReducer, {})
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, {})
.whenActionIsDispatched(changeModeAction({ exploreId: ExploreId.left, mode: ExploreMode.Logs }))
.thenStateShouldEqual({
mode: ExploreMode.Logs,
.thenStatePredicateShouldEqual((resultingState: ExploreItemState) => {
expect(resultingState.mode).toEqual(ExploreMode.Logs);
expect(resultingState.logsResult).toBeNull();
expect(resultingState.graphResult).toBeNull();
expect(resultingState.tableResult).toBeNull();
return true;
});
});
});
......
......@@ -183,8 +183,15 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
.addMapper({
filter: changeModeAction,
mapper: (state, action): ExploreItemState => {
const mode = action.payload.mode;
return { ...state, mode };
return {
...state,
mode: action.payload.mode,
graphResult: null,
tableResult: null,
logsResult: null,
queryResponse: createEmptyQueryResponse(),
loading: false,
};
},
})
.addMapper({
......
......@@ -9,7 +9,6 @@ import { beforeEach } from 'test/lib/common';
import { makeMockLokiDatasource } from './mocks';
import LokiDatasource from './datasource';
import { FUNCTIONS } from './syntax';
jest.mock('app/store/store', () => ({
store: {
......@@ -31,18 +30,17 @@ describe('Language completion provider', () => {
to: 1560163909000,
};
describe('empty query suggestions', () => {
it('returns function suggestions on empty context', async () => {
describe('query suggestions', () => {
it('returns no suggestions on empty context', async () => {
const instance = new LanguageProvider(datasource);
const value = Plain.deserialize('');
const result = await instance.provideCompletionItems({ text: '', prefix: '', value, wrapperClasses: [] });
expect(result.context).toBeUndefined();
expect(result.suggestions.length).toEqual(1);
expect(result.suggestions[0].label).toEqual('Functions');
expect(result.suggestions.length).toEqual(0);
});
it('returns function 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 value = Plain.deserialize('');
const history: LokiHistoryItem[] = [
......@@ -66,16 +64,13 @@ describe('Language completion provider', () => {
},
],
},
{
label: 'Functions',
items: FUNCTIONS,
},
]);
});
it('returns function suggestions within regexp', async () => {
it('returns function and history suggestions', async () => {
const instance = new LanguageProvider(datasource);
const input = createTypeaheadInput('{} ()', '', undefined, 4, []);
const input = createTypeaheadInput('m', 'm', undefined, 1, [], instance);
// Historic expressions don't have to match input, filtering is done in field
const history: LokiHistoryItem[] = [
{
query: { refId: '1', expr: '{app="foo"}' },
......@@ -84,8 +79,9 @@ describe('Language completion provider', () => {
];
const result = await instance.provideCompletionItems(input, { history });
expect(result.context).toBeUndefined();
expect(result.suggestions.length).toEqual(1);
expect(result.suggestions[0].label).toEqual('Functions');
expect(result.suggestions.length).toEqual(2);
expect(result.suggestions[0].label).toEqual('History');
expect(result.suggestions[1].label).toEqual('Functions');
});
});
......@@ -248,14 +244,15 @@ function createTypeaheadInput(
text: string,
labelKey?: string,
anchorOffset?: number,
wrapperClasses?: string[]
wrapperClasses?: string[],
instance?: LanguageProvider
): TypeaheadInput {
const deserialized = Plain.deserialize(value);
const range = deserialized.selection.setAnchor(deserialized.selection.anchor.setOffset(anchorOffset || 1));
const valueWithSelection = deserialized.setSelection(range);
return {
text,
prefix: '',
prefix: instance ? instance.cleanText(text) : '',
wrapperClasses: wrapperClasses || ['context-labels'],
value: valueWithSelection,
labelKey,
......
......@@ -13,7 +13,6 @@ import { RATE_RANGES } from '../prometheus/promql';
import LokiDatasource from './datasource';
import { CompletionItem, TypeaheadInput, TypeaheadOutput } from '@grafana/ui';
import { ExploreMode } from 'app/types/explore';
const DEFAULT_KEYS = ['job', 'namespace'];
const EMPTY_SELECTOR = '{}';
......@@ -130,8 +129,8 @@ export default class LokiLanguageProvider extends LanguageProvider {
// Prevent suggestions in `function(|suffix)`
const noSuffix = !nextCharacter || nextCharacter === ')';
// 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;
// Prefix is safe if it does not immediately follow a complete expression and has no text after it
const safePrefix = prefix && !text.match(/^['"~=\]})\s]+$/) && noSuffix;
// About to type next operand if preceded by binary operator
const operatorsPattern = /[+\-*/^%]/;
......@@ -145,8 +144,12 @@ export default class LokiLanguageProvider extends LanguageProvider {
// Suggestions for {|} and {foo=|}
return await this.getLabelCompletionItems(input, context);
} else if (empty) {
return this.getEmptyCompletionItems(context || {}, ExploreMode.Metrics);
} else if ((prefixUnrecognized && noSuffix) || safeEmptyPrefix || isNextOperand) {
// Suggestions for empty query field
return this.getEmptyCompletionItems(context);
} 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
return this.getTermCompletionItems();
}
......@@ -156,8 +159,14 @@ export default class LokiLanguageProvider extends LanguageProvider {
};
}
getEmptyCompletionItems(context: TypeaheadContext, mode?: ExploreMode): TypeaheadOutput {
const { history } = context;
getBeginningCompletionItems = (context: TypeaheadContext): TypeaheadOutput => {
return {
suggestions: [...this.getEmptyCompletionItems(context).suggestions, ...this.getTermCompletionItems().suggestions],
};
};
getEmptyCompletionItems(context: TypeaheadContext): TypeaheadOutput {
const history = context?.history;
const suggestions = [];
if (history && history.length) {
......@@ -178,11 +187,6 @@ export default class LokiLanguageProvider extends LanguageProvider {
});
}
if (mode === ExploreMode.Metrics) {
const termCompletionItems = this.getTermCompletionItems();
suggestions.push(...termCompletionItems.suggestions);
}
return { suggestions };
}
......
......@@ -146,7 +146,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
// Prevent suggestions in `function(|suffix)`
const noSuffix = !nextCharacter || nextCharacter === ')';
// Empty prefix is safe if it does not immediately follow a complete expression and has no text after it
// Prefix is safe if it does not immediately follow a complete expression and has no text after it
const safePrefix = prefix && !text.match(/^[\]})\s]+$/) && noSuffix;
// About to type next operand if preceded by binary operator
......
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