diff --git a/public/app/features/explore-map/components/Drilldown/ExploreMapDrilldownPanel.tsx b/public/app/features/explore-map/components/Drilldown/ExploreMapDrilldownPanel.tsx new file mode 100644 index 00000000000..2009c1690af --- /dev/null +++ b/public/app/features/explore-map/components/Drilldown/ExploreMapDrilldownPanel.tsx @@ -0,0 +1,161 @@ +import { css } from '@emotion/css'; +import { useEffect, useRef, useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { config } from '@grafana/runtime'; +import { useStyles2 } from '@grafana/ui'; +import { useDispatch, useSelector } from 'app/types/store'; + +import { updatePanelIframeUrl } from '../../state/crdtSlice'; +import { selectPanels } from '../../state/selectors'; + +interface ExploreMapDrilldownPanelProps { + exploreId: string; + width: number; + height: number; + mode: 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown'; + appPath: string; + titleKey: string; + titleDefault: string; +} + +export function ExploreMapDrilldownPanel({ + exploreId, + width, + height, + mode, + appPath, + titleKey, + titleDefault, +}: ExploreMapDrilldownPanelProps) { + const styles = useStyles2(getStyles); + const dispatch = useDispatch(); + const [isInitialized, setIsInitialized] = useState(false); + const iframeRef = useRef(null); + + // Find the panel with this exploreId to get saved state + const panel = useSelector((state) => { + const panels = selectPanels(state.exploreMapCRDT); + return Object.values(panels).find((p) => p.exploreId === exploreId); + }); + + // Build iframe URL - use saved URL if available, otherwise default + // IMPORTANT: Only compute this once on mount to prevent iframe reloads + const [drilldownUrl] = useState(() => { + // If we have a saved iframe URL from previous session, use it + if (panel?.mode === mode && panel?.iframeUrl) { + return panel.iframeUrl; + } + // Otherwise, construct the default URL + const origin = window.location.origin; + const subUrl = config.appSubUrl || ''; + return `${origin}${subUrl}${appPath}`; + }); + + // Initialize immediately for drilldown panels + useEffect(() => { + setIsInitialized(true); + }, []); + + // Auto-save iframe URL changes for drilldown panels + useEffect(() => { + if (!isInitialized) { + return; + } + + const panelId = panel?.id; + if (!panelId) { + return; + } + + let lastSavedUrl = panel?.iframeUrl || ''; + + const checkAndSaveUrl = () => { + const iframe = iframeRef.current; + if (!iframe) { + return; + } + + try { + const currentUrl = iframe.contentWindow?.location.href; + if (currentUrl && currentUrl !== lastSavedUrl) { + lastSavedUrl = currentUrl; + dispatch( + updatePanelIframeUrl({ + panelId, + iframeUrl: currentUrl, + }) + ); + } else if (!currentUrl) { + console.log('No URL detected'); + } + } catch (e) { + console.error('Cannot read iframe URL:', e); + } + }; + + // Start checking periodically (every 2 seconds) + const intervalId = setInterval(checkAndSaveUrl, 2000); + + // Also check immediately + setTimeout(checkAndSaveUrl, 100); + + return () => { + clearInterval(intervalId); + }; + // Intentionally not including panel.iframeUrl to avoid re-creating interval on every save + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isInitialized, panel?.id, dispatch]); + + if (!isInitialized) { + return ( +
+
+ Initializing... +
+
+ ); + } + + return ( +
+