2025-04-01 10:38:02 +09:00

86 lines
3.1 KiB
TypeScript

import { useEffect, useRef } from 'react';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useAppNotification } from 'app/core/copy/appNotification';
import { addListener, ExploreQueryParams, useDispatch, useSelector } from 'app/types';
import { selectPanes } from '../../state/selectors';
import { parseURL } from './parseURL';
import { syncFromURL } from './synchronizer/fromURL';
import { initializeFromURL } from './synchronizer/init';
import { syncToURL, syncToURLPredicate } from './synchronizer/toURL';
export { getUrlStateFromPaneState } from './external.utils';
/**
* Bi-directionally syncs URL changes with Explore's state.
*/
export function useStateSync(params: ExploreQueryParams) {
const { location } = useGrafana();
const dispatch = useDispatch();
const panesState = useSelector(selectPanes);
const panesStateRef = useRef(panesState);
const orgId = useSelector((state) => state.user.orgId);
const prevParams = useRef(params);
const initState = useRef<'notstarted' | 'pending' | 'done'>('notstarted');
const paused = useRef(false);
const { warning } = useAppNotification();
useEffect(() => {
// This happens when the user navigates to an explore "empty page" while within Explore.
// ie. by clicking on the explore when explore is active.
if (!params.panes) {
initState.current = 'notstarted';
prevParams.current = params;
}
}, [params]);
useEffect(() => {
const unsubscribe = dispatch(
addListener({
predicate: (action) => syncToURLPredicate(paused, action),
effect: async (_, { cancelActiveListeners, delay, getState }) => {
// The following 2 lines will debounce updates to avoid creating history entries when rapid changes
// are committed to the store.
cancelActiveListeners();
await delay(200);
syncToURL(getState().explore, prevParams, initState, location);
},
})
);
// @ts-expect-error the return type of addListener is actually callable, but dispatch is not middleware-aware
return () => unsubscribe();
}, [dispatch, location]);
useEffect(() => {
panesStateRef.current = panesState;
}, [panesState]);
useEffect(() => {
const isURLOutOfSync = prevParams.current?.panes !== params.panes;
const [urlState, hasParseError] = parseURL(params);
hasParseError &&
warning(
'Could not parse Explore URL',
'The requested URL contains invalid parameters, a default Explore state has been loaded.'
);
// This happens when the user first navigates to explore.
// Here we want to initialize each pane initial data, wether it comes
// from the url or as a result of migrations.
if (!isURLOutOfSync && initState.current === 'notstarted') {
initState.current = 'pending';
initializeFromURL(urlState, initState, orgId, dispatch, location);
}
prevParams.current = params;
if (isURLOutOfSync && initState.current === 'done') {
syncFromURL(urlState, panesStateRef.current, dispatch);
}
}, [dispatch, orgId, location, params, warning]);
}