86 lines
3.1 KiB
TypeScript
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]);
|
|
}
|