Simplify Drilldowns

This commit is contained in:
Joey
2025-12-02 13:27:38 +00:00
parent f6e547a7e4
commit ecfe2bd4b2
6 changed files with 202 additions and 553 deletions
@@ -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<HTMLIFrameElement>(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 (
<div className={styles.container}>
<div className={styles.loading}>
<Trans i18nKey="explore-map.panel.initializing">Initializing...</Trans>
</div>
</div>
);
}
return (
<div
className={styles.container}
style={{
width: `${width}px`,
height: `${height}px`,
}}
>
<iframe
ref={iframeRef}
src={drilldownUrl}
title={t(titleKey, titleDefault)}
style={{
width: '100%',
height: '100%',
border: 'none',
}}
/>
</div>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'relative',
}),
loading: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.body.fontSize,
}),
};
};
@@ -1,14 +1,4 @@
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';
import { ExploreMapDrilldownPanel } from './ExploreMapDrilldownPanel';
interface ExploreMapLogsDrilldownPanelProps {
exploreId: string;
@@ -17,134 +7,16 @@ interface ExploreMapLogsDrilldownPanelProps {
}
export function ExploreMapLogsDrilldownPanel({ exploreId, width, height }: ExploreMapLogsDrilldownPanelProps) {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isInitialized, setIsInitialized] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(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 logs drilldown - use saved URL if available, otherwise default
// IMPORTANT: Only compute this once on mount to prevent iframe reloads
const [logsDrilldownUrl] = useState(() => {
// If we have a saved iframe URL from previous session, use it
if (panel?.mode === 'logs-drilldown' && panel?.iframeUrl) {
return panel.iframeUrl;
}
// Otherwise, construct the default URL
const origin = window.location.origin;
const subUrl = config.appSubUrl || '';
const appPath = '/a/grafana-lokiexplore-app';
return `${origin}${subUrl}${appPath}`;
});
// Initialize immediately for logs drilldown panels
useEffect(() => {
setIsInitialized(true);
}, []);
// Auto-save iframe URL changes for logs-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 (
<div className={styles.container}>
<div className={styles.loading}>
<Trans i18nKey="explore-map.panel.initializing">Initializing...</Trans>
</div>
</div>
);
}
return (
<div
className={styles.container}
style={{
width: `${width}px`,
height: `${height}px`,
}}
>
<iframe
ref={iframeRef}
src={logsDrilldownUrl}
title={t('explore-map.panel.logs-drilldown', 'Logs Drilldown')}
style={{
width: '100%',
height: '100%',
border: 'none',
}}
/>
</div>
<ExploreMapDrilldownPanel
exploreId={exploreId}
width={width}
height={height}
mode="logs-drilldown"
appPath="/a/grafana-lokiexplore-app"
titleKey="explore-map.panel.logs-drilldown"
titleDefault="Logs Drilldown"
/>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'relative',
}),
loading: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.body.fontSize,
}),
};
};
@@ -1,14 +1,4 @@
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';
import { ExploreMapDrilldownPanel } from './ExploreMapDrilldownPanel';
interface ExploreMapMetricsDrilldownPanelProps {
exploreId: string;
@@ -17,134 +7,16 @@ interface ExploreMapMetricsDrilldownPanelProps {
}
export function ExploreMapMetricsDrilldownPanel({ exploreId, width, height }: ExploreMapMetricsDrilldownPanelProps) {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isInitialized, setIsInitialized] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(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 (
<div className={styles.container}>
<div className={styles.loading}>
<Trans i18nKey="explore-map.panel.initializing">Initializing...</Trans>
</div>
</div>
);
}
return (
<div
className={styles.container}
style={{
width: `${width}px`,
height: `${height}px`,
}}
>
<iframe
ref={iframeRef}
src={metricsDrilldownUrl}
title={t('explore-map.panel.metrics-drilldown', 'Metrics Drilldown')}
style={{
width: '100%',
height: '100%',
border: 'none',
}}
/>
</div>
<ExploreMapDrilldownPanel
exploreId={exploreId}
width={width}
height={height}
mode="metrics-drilldown"
appPath="/a/grafana-metricsdrilldown-app"
titleKey="explore-map.panel.metrics-drilldown"
titleDefault="Metrics Drilldown"
/>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'relative',
}),
loading: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.body.fontSize,
}),
};
};
@@ -1,14 +1,4 @@
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';
import { ExploreMapDrilldownPanel } from './ExploreMapDrilldownPanel';
interface ExploreMapProfilesDrilldownPanelProps {
exploreId: string;
@@ -17,134 +7,16 @@ interface ExploreMapProfilesDrilldownPanelProps {
}
export function ExploreMapProfilesDrilldownPanel({ exploreId, width, height }: ExploreMapProfilesDrilldownPanelProps) {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isInitialized, setIsInitialized] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(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 profiles drilldown - use saved URL if available, otherwise default
// IMPORTANT: Only compute this once on mount to prevent iframe reloads
const [profilesDrilldownUrl] = useState(() => {
// If we have a saved iframe URL from previous session, use it
if (panel?.mode === 'profiles-drilldown' && panel?.iframeUrl) {
return panel.iframeUrl;
}
// Otherwise, construct the default URL
const origin = window.location.origin;
const subUrl = config.appSubUrl || '';
const appPath = '/a/grafana-pyroscope-app';
return `${origin}${subUrl}${appPath}`;
});
// Initialize immediately for profiles drilldown panels
useEffect(() => {
setIsInitialized(true);
}, []);
// Auto-save iframe URL changes for profiles-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 (
<div className={styles.container}>
<div className={styles.loading}>
<Trans i18nKey="explore-map.panel.initializing">Initializing...</Trans>
</div>
</div>
);
}
return (
<div
className={styles.container}
style={{
width: `${width}px`,
height: `${height}px`,
}}
>
<iframe
ref={iframeRef}
src={profilesDrilldownUrl}
title={t('explore-map.panel.profiles-drilldown', 'Profiles Drilldown')}
style={{
width: '100%',
height: '100%',
border: 'none',
}}
/>
</div>
<ExploreMapDrilldownPanel
exploreId={exploreId}
width={width}
height={height}
mode="profiles-drilldown"
appPath="/a/grafana-pyroscope-app"
titleKey="explore-map.panel.profiles-drilldown"
titleDefault="Profiles Drilldown"
/>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'relative',
}),
loading: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.body.fontSize,
}),
};
};
@@ -1,14 +1,4 @@
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';
import { ExploreMapDrilldownPanel } from './ExploreMapDrilldownPanel';
interface ExploreMapTracesDrilldownPanelProps {
exploreId: string;
@@ -17,134 +7,16 @@ interface ExploreMapTracesDrilldownPanelProps {
}
export function ExploreMapTracesDrilldownPanel({ exploreId, width, height }: ExploreMapTracesDrilldownPanelProps) {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isInitialized, setIsInitialized] = useState(false);
const iframeRef = useRef<HTMLIFrameElement>(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 traces drilldown - use saved URL if available, otherwise default
// IMPORTANT: Only compute this once on mount to prevent iframe reloads
const [tracesDrilldownUrl] = useState(() => {
// If we have a saved iframe URL from previous session, use it
if (panel?.mode === 'traces-drilldown' && panel?.iframeUrl) {
return panel.iframeUrl;
}
// Otherwise, construct the default URL
const origin = window.location.origin;
const subUrl = config.appSubUrl || '';
const appPath = '/a/grafana-exploretraces-app';
return `${origin}${subUrl}${appPath}`;
});
// Initialize immediately for traces drilldown panels
useEffect(() => {
setIsInitialized(true);
}, []);
// Auto-save iframe URL changes for traces-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 (
<div className={styles.container}>
<div className={styles.loading}>
<Trans i18nKey="explore-map.panel.initializing">Initializing...</Trans>
</div>
</div>
);
}
return (
<div
className={styles.container}
style={{
width: `${width}px`,
height: `${height}px`,
}}
>
<iframe
ref={iframeRef}
src={tracesDrilldownUrl}
title={t('explore-map.panel.traces-drilldown', 'Traces Drilldown')}
style={{
width: '100%',
height: '100%',
border: 'none',
}}
/>
</div>
<ExploreMapDrilldownPanel
exploreId={exploreId}
width={width}
height={height}
mode="traces-drilldown"
appPath="/a/grafana-exploretraces-app"
titleKey="explore-map.panel.traces-drilldown"
titleDefault="Traces Drilldown"
/>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({
width: '100%',
height: '100%',
overflow: 'hidden',
position: 'relative',
}),
loading: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: theme.colors.text.secondary,
fontSize: theme.typography.body.fontSize,
}),
};
};
@@ -480,7 +480,7 @@ const crdtSlice = createSlice({
width: sourcePanel.position.width,
height: sourcePanel.position.height,
},
(sourcePanel.mode || 'explore') as 'explore' | 'traces-drilldown' | 'metrics-drilldown'
(sourcePanel.mode || 'explore') as 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown'
);
manager.applyOperation(addOperation);