Show the creator of a panel

This commit is contained in:
Aleksandar Petrov
2025-12-02 10:21:50 -04:00
parent ba65f18678
commit f500500927
6 changed files with 58 additions and 13 deletions
@@ -4,6 +4,7 @@ import { useCallback, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { Button, ButtonGroup, Dropdown, Menu, MenuItem, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv';
import pyroscopeIconSvg from 'app/plugins/datasource/grafana-pyroscope-datasource/img/grafana_pyroscope_icon.svg';
import lokiIconSvg from 'app/plugins/datasource/loki/img/loki_icon.svg';
import prometheusLogoSvg from 'app/plugins/datasource/prometheus/img/prometheus_logo.svg';
@@ -16,6 +17,7 @@ export function ExploreMapFloatingToolbar() {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const [isOpen, setIsOpen] = useState(false);
const currentUsername = contextSrv.user.name || contextSrv.user.login || 'Unknown';
const handleAddPanel = useCallback(() => {
dispatch(
@@ -24,10 +26,11 @@ export function ExploreMapFloatingToolbar() {
width: window.innerWidth,
height: window.innerHeight,
},
createdBy: currentUsername,
})
);
setIsOpen(false);
}, [dispatch]);
}, [dispatch, currentUsername]);
const handleAddTracesDrilldownPanel = useCallback(() => {
dispatch(
@@ -37,10 +40,11 @@ export function ExploreMapFloatingToolbar() {
height: window.innerHeight,
},
kind: 'traces-drilldown',
createdBy: currentUsername,
})
);
setIsOpen(false);
}, [dispatch]);
}, [dispatch, currentUsername]);
const handleAddMetricsDrilldownPanel = useCallback(() => {
dispatch(
@@ -50,10 +54,11 @@ export function ExploreMapFloatingToolbar() {
height: window.innerHeight,
},
kind: 'metrics-drilldown',
createdBy: currentUsername,
})
);
setIsOpen(false);
}, [dispatch]);
}, [dispatch, currentUsername]);
const handleAddProfilesDrilldownPanel = useCallback(() => {
dispatch(
@@ -63,10 +68,11 @@ export function ExploreMapFloatingToolbar() {
height: window.innerHeight,
},
kind: 'profiles-drilldown',
createdBy: currentUsername,
})
);
setIsOpen(false);
}, [dispatch]);
}, [dispatch, currentUsername]);
const handleAddLogsDrilldownPanel = useCallback(() => {
dispatch(
@@ -76,10 +82,11 @@ export function ExploreMapFloatingToolbar() {
height: window.innerHeight,
},
kind: 'logs-drilldown',
createdBy: currentUsername,
})
);
setIsOpen(false);
}, [dispatch]);
}, [dispatch, currentUsername]);
const MenuActions = () => (
<Menu>
@@ -4,7 +4,7 @@ import { Rnd, RndDragCallback, RndResizeCallback } from 'react-rnd';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Button, useStyles2 } from '@grafana/ui';
import { Button, Tooltip, useStyles2 } from '@grafana/ui';
import { useDispatch, useSelector } from 'app/types/store';
import { splitClose } from '../../explore/state/main';
@@ -64,9 +64,6 @@ function ExploreMapPanelContainerComponent({ panel }: ExploreMapPanelContainerPr
return;
}
const deltaX = data.x - dragStartPos.x;
const deltaY = data.y - dragStartPos.y;
// If dragging multiple panels, store the drag offset in local state
// This will cause other panels to visually move without CRDT operations
if (isSelected && selectedPanelIds.length > 1) {
@@ -190,6 +187,18 @@ function ExploreMapPanelContainerComponent({ panel }: ExploreMapPanelContainerPr
[dispatch, panel.id]
);
const handleInfoClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
}, []);
// Build tooltip content for panel info
const getInfoTooltipContent = useCallback(() => {
if (panel.createdBy) {
return t('explore-map.panel.info.created-by', 'Created by: {{creator}}', { creator: panel.createdBy });
}
return t('explore-map.panel.info.no-creator', 'Creator unknown');
}, [panel.createdBy]);
return (
<Rnd
ref={rndRef}
@@ -214,6 +223,16 @@ function ExploreMapPanelContainerComponent({ panel }: ExploreMapPanelContainerPr
{t('explore-map.panel.title', 'Explore Panel {{id}}', { id: panel.id.slice(0, 8) })}
</div>
<div className={styles.panelActions}>
<Tooltip content={getInfoTooltipContent()} placement="bottom">
<Button
icon="info-circle"
variant="secondary"
size="sm"
fill="text"
onClick={handleInfoClick}
aria-label={t('explore-map.panel.info', 'Panel information')}
/>
</Tooltip>
<Button
icon="copy"
variant="secondary"
+10 -2
View File
@@ -121,6 +121,7 @@ export class CRDTStateManager {
exploreState: data.exploreState.get(),
mode: data.mode.get(),
iframeUrl: data.iframeUrl.get(),
createdBy: data.createdBy.get(),
remoteVersion: data.remoteVersion,
};
}
@@ -146,7 +147,8 @@ export class CRDTStateManager {
panelId: string,
exploreId: string,
position: { x: number; y: number; width: number; height: number },
mode: 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown' = 'explore'
mode: 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown' = 'explore',
createdBy?: string
): AddPanelOperation {
const timestamp = this.clock.tick();
return {
@@ -160,6 +162,7 @@ export class CRDTStateManager {
exploreId,
position,
mode,
createdBy,
},
};
}
@@ -451,7 +454,7 @@ export class CRDTStateManager {
}
private applyAddPanel(operation: AddPanelOperation): OperationResult {
const { panelId, exploreId, position, mode } = operation.payload;
const { panelId, exploreId, position, mode, createdBy } = operation.payload;
const panelMode = mode || 'explore';
// Add to OR-Set with operation ID as tag
@@ -472,6 +475,7 @@ export class CRDTStateManager {
exploreState: new LWWRegister(undefined, operation.timestamp),
mode: new LWWRegister(panelMode, operation.timestamp),
iframeUrl: new LWWRegister(undefined, operation.timestamp),
createdBy: new LWWRegister(createdBy, operation.timestamp),
remoteVersion: 0,
});
}
@@ -697,6 +701,7 @@ export class CRDTStateManager {
exploreState: otherPanelData.exploreState.clone(),
mode: otherPanelData.mode.clone(),
iframeUrl: otherPanelData.iframeUrl.clone(),
createdBy: otherPanelData.createdBy.clone(),
remoteVersion: otherPanelData.remoteVersion,
});
} else {
@@ -709,6 +714,7 @@ export class CRDTStateManager {
myPanelData.exploreState.merge(otherPanelData.exploreState);
myPanelData.mode.merge(otherPanelData.mode);
myPanelData.iframeUrl.merge(otherPanelData.iframeUrl);
myPanelData.createdBy.merge(otherPanelData.createdBy);
}
}
@@ -734,6 +740,7 @@ export class CRDTStateManager {
exploreState: data.exploreState.toJSON(),
mode: data.mode.toJSON(),
iframeUrl: data.iframeUrl.toJSON(),
createdBy: data.createdBy.toJSON(),
remoteVersion: data.remoteVersion,
};
}
@@ -789,6 +796,7 @@ export class CRDTStateManager {
exploreState: LWWRegister.fromJSON(data.exploreState),
mode: data.mode ? LWWRegister.fromJSON(data.mode) : new LWWRegister('explore', defaultTimestamp),
iframeUrl: data.iframeUrl ? LWWRegister.fromJSON(data.iframeUrl) : new LWWRegister(undefined, defaultTimestamp),
createdBy: data.createdBy ? LWWRegister.fromJSON(data.createdBy) : new LWWRegister(undefined, defaultTimestamp),
remoteVersion: data.remoteVersion || 0,
});
}
@@ -36,6 +36,9 @@ export interface CRDTPanelData {
// Iframe URL for traces-drilldown panels
iframeUrl: LWWRegister<string | undefined>;
// Creator metadata (username of who created the panel)
createdBy: LWWRegister<string | undefined>;
// Local counter incremented only for remote explore state updates
remoteVersion: number;
}
@@ -117,6 +120,7 @@ export interface CRDTExploreMapStateJSON {
exploreState: { value: SerializedExploreState | undefined; timestamp: HLCTimestamp };
mode: { value: 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown'; timestamp: HLCTimestamp };
iframeUrl: { value: string | undefined; timestamp: HLCTimestamp };
createdBy?: { value: string | undefined; timestamp: HLCTimestamp };
remoteVersion?: number;
}>;
zIndexCounter: {
@@ -167,6 +171,7 @@ export interface AddPanelOperation extends CRDTOperationBase {
height: number;
};
mode?: 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown';
createdBy?: string;
};
}
@@ -217,6 +217,7 @@ const crdtSlice = createSlice({
viewportSize?: { width: number; height: number };
position?: { x: number; y: number; width: number; height: number };
kind?: 'explore' | 'traces-drilldown' | 'metrics-drilldown' | 'profiles-drilldown' | 'logs-drilldown';
createdBy?: string;
}>) => {
const manager = getCRDTManager(state);
@@ -253,7 +254,7 @@ const crdtSlice = createSlice({
const panelId = uuidv4();
const exploreId = generateExploreId();
const operation = manager.createAddPanelOperation(panelId, exploreId, position, mode);
const operation = manager.createAddPanelOperation(panelId, exploreId, position, mode, action.payload.createdBy);
// Apply locally
manager.applyOperation(operation);
@@ -513,7 +514,8 @@ const crdtSlice = createSlice({
width: sourcePanel.position.width,
height: sourcePanel.position.height,
},
sourcePanel.mode || 'explore'
sourcePanel.mode || 'explore',
sourcePanel.createdBy
);
manager.applyOperation(addOperation);
@@ -37,6 +37,10 @@ export interface ExploreMapPanel {
* including query parameters to restore state on reload
*/
iframeUrl?: string;
/**
* Username of the user who created this panel
*/
createdBy?: string;
}
export interface CanvasViewport {