diff --git a/public/app/features/explore/state/actions.ts b/public/app/features/explore/state/actions.ts index b1f8da27ed4..3edf952ba02 100644 --- a/public/app/features/explore/state/actions.ts +++ b/public/app/features/explore/state/actions.ts @@ -153,7 +153,6 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun */ export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult { return dispatch => { - dispatch(clearQueriesAction({ exploreId })); dispatch(changeModeAction({ exploreId, mode })); }; } diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 28d51afa526..9c75ade7703 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -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>, {}) .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; }); }); }); diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 9a165c761e6..4a44f36ac62 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -183,8 +183,15 @@ export const itemReducer = reducerFactory({} 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({ diff --git a/public/app/plugins/datasource/loki/language_provider.test.ts b/public/app/plugins/datasource/loki/language_provider.test.ts index 7bcce3afca7..0a3a9d9f57e 100644 --- a/public/app/plugins/datasource/loki/language_provider.test.ts +++ b/public/app/plugins/datasource/loki/language_provider.test.ts @@ -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, diff --git a/public/app/plugins/datasource/loki/language_provider.ts b/public/app/plugins/datasource/loki/language_provider.ts index a1c8d757104..38b730389cc 100644 --- a/public/app/plugins/datasource/loki/language_provider.ts +++ b/public/app/plugins/datasource/loki/language_provider.ts @@ -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 }; } diff --git a/public/app/plugins/datasource/prometheus/language_provider.ts b/public/app/plugins/datasource/prometheus/language_provider.ts index 79e2619ca51..7e616dd066c 100644 --- a/public/app/plugins/datasource/prometheus/language_provider.ts +++ b/public/app/plugins/datasource/prometheus/language_provider.ts @@ -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