Explore: Fix URL sync with async queries import (#79584)

* cancel pending queries

* Update public/app/features/explore/hooks/useStateSync/index.ts

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>

* Update public/app/features/explore/hooks/useStateSync/index.ts

* Fix test

---------

Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
Giordano Ricci 2024-01-04 09:50:27 +00:00 committed by GitHub
parent 5ae3249c36
commit 2d18961768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 34 deletions

View File

@ -57,7 +57,9 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl
) { ) {
const { exploreId, changeDatasourceUid } = correlationDetails?.postConfirmAction; const { exploreId, changeDatasourceUid } = correlationDetails?.postConfirmAction;
if (exploreId && changeDatasourceUid) { if (exploreId && changeDatasourceUid) {
dispatch(changeDatasource(exploreId, changeDatasourceUid, { importQueries: true })); dispatch(
changeDatasource({ exploreId, datasource: changeDatasourceUid, options: { importQueries: true } })
);
dispatch( dispatch(
changeCorrelationEditorDetails({ changeCorrelationEditorDetails({
isExiting: false, isExiting: false,
@ -143,7 +145,7 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl
const changeDatasourcePostAction = (exploreId: string, datasourceUid: string) => { const changeDatasourcePostAction = (exploreId: string, datasourceUid: string) => {
setSaveMessage(undefined); setSaveMessage(undefined);
dispatch(changeDatasource(exploreId, datasourceUid, { importQueries: true })); dispatch(changeDatasource({ exploreId, datasource: datasourceUid, options: { importQueries: true } }));
}; };
const saveCorrelationPostAction = (skipPostConfirmAction: boolean) => { const saveCorrelationPostAction = (skipPostConfirmAction: boolean) => {
@ -163,7 +165,7 @@ export const CorrelationEditorModeBar = ({ panes }: { panes: Array<[string, Expl
action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE && action === CORRELATION_EDITOR_POST_CONFIRM_ACTION.CHANGE_DATASOURCE &&
changeDatasourceUid !== undefined changeDatasourceUid !== undefined
) { ) {
changeDatasource(exploreId, changeDatasourceUid); changeDatasource({ exploreId, datasource: changeDatasourceUid });
resetEditor(); resetEditor();
} }
} else { } else {

View File

@ -100,7 +100,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
const onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => { const onChangeDatasource = async (dsSettings: DataSourceInstanceSettings) => {
if (!isCorrelationsEditorMode) { if (!isCorrelationsEditorMode) {
dispatch(changeDatasource(exploreId, dsSettings.uid, { importQueries: true })); dispatch(changeDatasource({ exploreId, datasource: dsSettings.uid, options: { importQueries: true } }));
} else { } else {
if (correlationDetails?.correlationDirty || correlationDetails?.queryEditorDirty) { if (correlationDetails?.correlationDirty || correlationDetails?.queryEditorDirty) {
// prompt will handle datasource change if needed // prompt will handle datasource change if needed
@ -128,7 +128,7 @@ export function ExploreToolbar({ exploreId, onChangeTime, onContentOutlineToogle
}); });
} }
dispatch(changeDatasource(exploreId, dsSettings.uid, { importQueries: true })); dispatch(changeDatasource({ exploreId, datasource: dsSettings.uid, options: { importQueries: true } }));
} }
} }
}; };

View File

@ -374,7 +374,7 @@ describe('RichHistoryCard', () => {
await waitFor(() => { await waitFor(() => {
expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries); expect(setQueries).toHaveBeenCalledWith(expect.any(String), queries);
expect(changeDatasource).toHaveBeenCalledWith(expect.any(String), 'mixed'); expect(changeDatasource).toHaveBeenCalledWith({ datasource: 'mixed', exploreId: 'left' });
}); });
}); });
}); });

View File

@ -188,7 +188,7 @@ export function RichHistoryCard(props: Props) {
const queriesToRun = queryHistoryItem.queries; const queriesToRun = queryHistoryItem.queries;
const differentDataSource = queryHistoryItem.datasourceUid !== datasourceInstance?.uid; const differentDataSource = queryHistoryItem.datasourceUid !== datasourceInstance?.uid;
if (differentDataSource) { if (differentDataSource) {
await changeDatasource(exploreId, queryHistoryItem.datasourceUid); await changeDatasource({ exploreId, datasource: queryHistoryItem.datasourceUid });
} }
setQueries(exploreId, queriesToRun); setQueries(exploreId, queriesToRun);

View File

@ -13,7 +13,7 @@ import { addListener, ExploreItemState, ExploreQueryParams, useDispatch, useSele
import { changeDatasource } from '../../state/datasource'; import { changeDatasource } from '../../state/datasource';
import { changePanelsStateAction, initializeExplore } from '../../state/explorePane'; import { changePanelsStateAction, initializeExplore } from '../../state/explorePane';
import { clearPanes, splitClose, splitOpen, syncTimesAction } from '../../state/main'; import { clearPanes, splitClose, splitOpen, syncTimesAction } from '../../state/main';
import { runQueries, setQueriesAction } from '../../state/query'; import { cancelQueries, runQueries, setQueriesAction } from '../../state/query';
import { selectPanes } from '../../state/selectors'; import { selectPanes } from '../../state/selectors';
import { changeRangeAction, updateTime } from '../../state/time'; import { changeRangeAction, updateTime } from '../../state/time';
import { DEFAULT_RANGE, fromURLRange } from '../../state/utils'; import { DEFAULT_RANGE, fromURLRange } from '../../state/utils';
@ -32,6 +32,7 @@ export function useStateSync(params: ExploreQueryParams) {
const orgId = useSelector((state) => state.user.orgId); const orgId = useSelector((state) => state.user.orgId);
const prevParams = useRef(params); const prevParams = useRef(params);
const initState = useRef<'notstarted' | 'pending' | 'done'>('notstarted'); const initState = useRef<'notstarted' | 'pending' | 'done'>('notstarted');
const paused = useRef(false);
const { warning } = useAppNotification(); const { warning } = useAppNotification();
useEffect(() => { useEffect(() => {
@ -47,20 +48,35 @@ export function useStateSync(params: ExploreQueryParams) {
const unsubscribe = dispatch( const unsubscribe = dispatch(
addListener({ addListener({
predicate: (action) => predicate: (action) =>
// We want to update the URL when: /*
// - a pane is opened or closed We want to update the URL when:
// - a query is run - a pane is opened or closed
// - range is changed - a query is run
// - panel state is updated - range is changed
[ - panel state is updated
splitClose.type, - a datasource change has completed.
splitOpen.fulfilled.type,
runQueries.pending.type, Note: Changing datasource causes a bunch of actions to be dispatched, we want to update the URL
changeRangeAction.type, only when the change set has completed. This is done by checking if the changeDatasource.pending action
changePanelsStateAction.type, has been dispatched and pausing the listener until the changeDatasource.fulfilled action is dispatched.
].includes(action.type), */
{
paused.current = changeDatasource.pending.type === action.type;
return (
[
splitClose.type,
splitOpen.fulfilled.type,
runQueries.pending.type,
changeRangeAction.type,
changePanelsStateAction.type,
changeDatasource.fulfilled.type,
].includes(action.type) && !paused.current
);
},
effect: async (_, { cancelActiveListeners, delay, getState }) => { effect: async (_, { cancelActiveListeners, delay, getState }) => {
// The following 2 lines will throttle updates to avoid creating history entries when rapid changes // The following 2 lines will debounce updates to avoid creating history entries when rapid changes
// are committed to the store. // are committed to the store.
cancelActiveListeners(); cancelActiveListeners();
await delay(200); await delay(200);
@ -118,7 +134,7 @@ export function useStateSync(params: ExploreQueryParams) {
dispatch(syncTimesAction({ syncedTimes: !paneTimesUnequal })); // if all time ranges are equal, keep them synced dispatch(syncTimesAction({ syncedTimes: !paneTimesUnequal })); // if all time ranges are equal, keep them synced
} }
Object.entries(urlState.panes).forEach(([exploreId, urlPane], i) => { Object.entries(urlState.panes).forEach(async ([exploreId, urlPane], i) => {
const { datasource, queries, range, panelsState } = urlPane; const { datasource, queries, range, panelsState } = urlPane;
const paneState = panesState[exploreId]; const paneState = panesState[exploreId];
@ -129,11 +145,11 @@ export function useStateSync(params: ExploreQueryParams) {
Promise.resolve() Promise.resolve()
.then(async () => { .then(async () => {
if (update.datasource && datasource) { if (update.datasource && datasource) {
await dispatch(changeDatasource(exploreId, datasource)); await dispatch(changeDatasource({ exploreId, datasource }));
} }
return; return;
}) })
.then(() => { .then(async () => {
if (update.range) { if (update.range) {
dispatch(updateTime({ exploreId, rawRange: fromURLRange(range) })); dispatch(updateTime({ exploreId, rawRange: fromURLRange(range) }));
} }
@ -143,6 +159,7 @@ export function useStateSync(params: ExploreQueryParams) {
} }
if (update.queries || update.range) { if (update.queries || update.range) {
await dispatch(cancelQueries(exploreId));
dispatch(runQueries({ exploreId })); dispatch(runQueries({ exploreId }));
} }
@ -281,7 +298,9 @@ export function useStateSync(params: ExploreQueryParams) {
prevParams.current = params; prevParams.current = params;
isURLOutOfSync && initState.current === 'done' && sync(); if (isURLOutOfSync && initState.current === 'done') {
sync();
}
}, [dispatch, panesState, orgId, location, params, warning]); }, [dispatch, panesState, orgId, location, params, warning]);
} }

View File

@ -7,7 +7,7 @@ import { DataSourceRef } from '@grafana/schema';
import { RefreshPicker } from '@grafana/ui'; import { RefreshPicker } from '@grafana/ui';
import { stopQueryState } from 'app/core/utils/explore'; import { stopQueryState } from 'app/core/utils/explore';
import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils'; import { getCorrelationsBySourceUIDs } from 'app/features/correlations/utils';
import { ExploreItemState, ThunkResult } from 'app/types'; import { ExploreItemState, createAsyncThunk } from 'app/types';
import { loadSupplementaryQueries } from '../utils/supplementaryQueries'; import { loadSupplementaryQueries } from '../utils/supplementaryQueries';
@ -39,12 +39,15 @@ export const updateDatasourceInstanceAction = createAction<UpdateDatasourceInsta
/** /**
* Loads a new datasource identified by the given name. * Loads a new datasource identified by the given name.
*/ */
export function changeDatasource(
exploreId: string, interface ChangeDatasourcePayload {
datasource: string | DataSourceRef, exploreId: string;
options?: { importQueries: boolean } datasource: string | DataSourceRef;
): ThunkResult<Promise<void>> { options?: { importQueries: boolean };
return async (dispatch, getState) => { }
export const changeDatasource = createAsyncThunk(
'explore/changeDatasource',
async ({ datasource, exploreId, options }: ChangeDatasourcePayload, { getState, dispatch }) => {
const orgId = getState().user.orgId; const orgId = getState().user.orgId;
const { history, instance } = await loadAndInitDatasource(orgId, datasource); const { history, instance } = await loadAndInitDatasource(orgId, datasource);
const currentDataSourceInstance = getState().explore.panes[exploreId]!.datasourceInstance; const currentDataSourceInstance = getState().explore.panes[exploreId]!.datasourceInstance;
@ -80,8 +83,8 @@ export function changeDatasource(
if (options?.importQueries) { if (options?.importQueries) {
dispatch(runQueries({ exploreId })); dispatch(runQueries({ exploreId }));
} }
}; }
} );
// //
// Reducer // Reducer