From b1b0af06e71b45833148eb0b6c3c64f59621cb18 Mon Sep 17 00:00:00 2001 From: Joey Date: Tue, 2 Dec 2025 09:40:36 +0000 Subject: [PATCH] Add Metrics Drilldown --- .../ExploreMapMetricsDrilldownPanel.tsx | 150 ++++++++++++++++++ .../ExploreMapTracesDrilldownPanel.tsx | 4 +- .../components/ExploreMapFloatingToolbar.tsx | 18 +++ .../components/ExploreMapPanelContent.tsx | 11 +- public/app/features/explore-map/crdt/state.ts | 2 +- public/app/features/explore-map/crdt/types.ts | 8 +- .../features/explore-map/state/crdtSlice.ts | 11 +- .../app/features/explore-map/state/types.ts | 3 +- 8 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 public/app/features/explore-map/components/Drilldown/ExploreMapMetricsDrilldownPanel.tsx rename public/app/features/explore-map/components/{ => Drilldown}/ExploreMapTracesDrilldownPanel.tsx (97%) diff --git a/public/app/features/explore-map/components/Drilldown/ExploreMapMetricsDrilldownPanel.tsx b/public/app/features/explore-map/components/Drilldown/ExploreMapMetricsDrilldownPanel.tsx new file mode 100644 index 00000000000..028523e37a3 --- /dev/null +++ b/public/app/features/explore-map/components/Drilldown/ExploreMapMetricsDrilldownPanel.tsx @@ -0,0 +1,150 @@ +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 ExploreMapMetricsDrilldownPanelProps { + exploreId: string; + width: number; + height: number; +} + +export function ExploreMapMetricsDrilldownPanel({ exploreId, width, height }: ExploreMapMetricsDrilldownPanelProps) { + 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 for metrics drilldown - use saved URL if available, otherwise default + // IMPORTANT: Only compute this once on mount to prevent iframe reloads + const [metricsDrilldownUrl] = useState(() => { + // If we have a saved iframe URL from previous session, use it + if (panel?.mode === 'metrics-drilldown' && panel?.iframeUrl) { + return panel.iframeUrl; + } + // Otherwise, construct the default URL + const origin = window.location.origin; + const subUrl = config.appSubUrl || ''; + const appPath = '/a/grafana-metricsdrilldown-app'; + return `${origin}${subUrl}${appPath}`; + }); + + // Initialize immediately for metrics drilldown panels + useEffect(() => { + setIsInitialized(true); + }, []); + + // Auto-save iframe URL changes for metrics-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 ( +
+