mirror of
https://github.com/grafana/grafana.git
synced 2025-01-09 07:33:42 -06:00
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
This commit is contained in:
parent
334b89f3ee
commit
3667781e6f
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user