diff --git a/public/app/features/explore/state/query.test.ts b/public/app/features/explore/state/query.test.ts index 58795956e64..f228a80ff7f 100644 --- a/public/app/features/explore/state/query.test.ts +++ b/public/app/features/explore/state/query.test.ts @@ -3,12 +3,14 @@ import { addResultsToCache, cancelQueries, cancelQueriesAction, + cleanLogsVolumeAction, clearCache, importQueries, queryReducer, runQueries, scanStartAction, scanStopAction, + storeLogsVolumeDataProviderAction, } from './query'; import { ExploreId, ExploreItemState, StoreState, ThunkDispatch } from 'app/types'; import { interval, Observable, of } from 'rxjs'; @@ -127,7 +129,12 @@ describe('running queries', () => { .givenThunk(cancelQueries) .whenThunkIsDispatched(exploreId); - expect(dispatchedActions).toEqual([scanStopAction({ exploreId }), cancelQueriesAction({ exploreId })]); + expect(dispatchedActions).toEqual([ + scanStopAction({ exploreId }), + cancelQueriesAction({ exploreId }), + storeLogsVolumeDataProviderAction({ exploreId, logsVolumeDataProvider: undefined }), + cleanLogsVolumeAction({ exploreId }), + ]); }); }); @@ -309,18 +316,24 @@ describe('reducer', () => { }); }); - describe('logs volume', () => { + describe('log volume', () => { let dispatch: ThunkDispatch, getState: () => StoreState, unsubscribes: Function[], mockLogsVolumeDataProvider: () => Observable; beforeEach(() => { + unsubscribes = []; mockLogsVolumeDataProvider = () => { - return of( - { state: LoadingState.Loading, error: undefined, data: [] }, - { state: LoadingState.Done, error: undefined, data: [{}] } - ); + return ({ + subscribe: () => { + const unsubscribe = jest.fn(); + unsubscribes.push(unsubscribe); + return { + unsubscribe, + }; + }, + } as unknown) as Observable; }; const store: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore({ @@ -346,22 +359,9 @@ describe('reducer', () => { getState = store.getState; setupQueryResponse(getState()); - unsubscribes = []; - - mockLogsVolumeDataProvider = () => { - return ({ - subscribe: () => { - const unsubscribe = jest.fn(); - unsubscribes.push(unsubscribe); - return { - unsubscribe, - }; - }, - } as unknown) as Observable; - }; }); - it('should cancel any unfinished logs volume queries', async () => { + it('should cancel any unfinished logs volume queries when a new query is run', async () => { await dispatch(runQueries(ExploreId.left)); // first query is run automatically // loading in progress - one subscription created, not cleaned up yet @@ -377,9 +377,56 @@ describe('reducer', () => { expect(unsubscribes[1]).not.toBeCalled(); }); + it('should cancel log volume query when the main query is canceled', async () => { + await dispatch(runQueries(ExploreId.left)); + expect(unsubscribes).toHaveLength(1); + expect(unsubscribes[0]).not.toBeCalled(); + + await dispatch(cancelQueries(ExploreId.left)); + expect(unsubscribes).toHaveLength(1); + expect(unsubscribes[0]).toBeCalled(); + + expect(getState().explore[ExploreId.left].logsVolumeData).toBeUndefined(); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined(); + }); + it('should load logs volume after running the query', async () => { await dispatch(runQueries(ExploreId.left)); expect(unsubscribes).toHaveLength(1); }); + + it('should clean any incomplete log volume data when main query is canceled', async () => { + mockLogsVolumeDataProvider = () => { + return of({ state: LoadingState.Loading, error: undefined, data: [] }); + }; + await dispatch(runQueries(ExploreId.left)); + + expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined(); + expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Loading); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined(); + + await dispatch(cancelQueries(ExploreId.left)); + expect(getState().explore[ExploreId.left].logsVolumeData).toBeUndefined(); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined(); + }); + + it('keeps complete log volume data when main query is canceled', async () => { + mockLogsVolumeDataProvider = () => { + return of( + { state: LoadingState.Loading, error: undefined, data: [] }, + { state: LoadingState.Done, error: undefined, data: [{}] } + ); + }; + await dispatch(runQueries(ExploreId.left)); + + expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined(); + expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Done); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeDefined(); + + await dispatch(cancelQueries(ExploreId.left)); + expect(getState().explore[ExploreId.left].logsVolumeData).toBeDefined(); + expect(getState().explore[ExploreId.left].logsVolumeData!.state).toBe(LoadingState.Done); + expect(getState().explore[ExploreId.left].logsVolumeDataProvider).toBeUndefined(); + }); }); }); diff --git a/public/app/features/explore/state/query.ts b/public/app/features/explore/state/query.ts index 8467a0be6c5..eadb3c6e729 100644 --- a/public/app/features/explore/state/query.ts +++ b/public/app/features/explore/state/query.ts @@ -110,11 +110,11 @@ export interface StoreLogsVolumeDataProvider { /** * Stores available logs volume provider after running the query. Used internally by runQueries(). */ -const storeLogsVolumeDataProviderAction = createAction( +export const storeLogsVolumeDataProviderAction = createAction( 'explore/storeLogsVolumeDataProviderAction' ); -const cleanLogsVolumeAction = createAction<{ exploreId: ExploreId }>('explore/cleanLogsVolumeAction'); +export const cleanLogsVolumeAction = createAction<{ exploreId: ExploreId }>('explore/cleanLogsVolumeAction'); export interface StoreLogsVolumeDataSubscriptionPayload { exploreId: ExploreId; @@ -221,9 +221,19 @@ export function addQueryRow(exploreId: ExploreId, index: number): ThunkResult { - return (dispatch) => { + return (dispatch, getState) => { dispatch(scanStopAction({ exploreId })); dispatch(cancelQueriesAction({ exploreId })); + dispatch( + storeLogsVolumeDataProviderAction({ + exploreId, + logsVolumeDataProvider: undefined, + }) + ); + // clear any incomplete data + if (getState().explore[exploreId]!.logsVolumeData?.state !== LoadingState.Done) { + dispatch(cleanLogsVolumeAction({ exploreId })); + } dispatch(stateSave()); }; }