2025-04-01 10:38:02 +09:00

178 lines
5.4 KiB
TypeScript

import { DataFrame, PanelMenuItem } from '@grafana/data';
import { getPluginLinkExtensions } from '@grafana/runtime';
import {
SceneComponentProps,
sceneGraph,
SceneObject,
SceneObjectBase,
SceneObjectState,
VizPanel,
VizPanelMenu,
} from '@grafana/scenes';
import { getExploreUrl } from 'app/core/utils/explore';
import { getQueryRunnerFor } from 'app/features/dashboard-scene/utils/utils';
import { AddToExplorationButton, extensionPointId } from '../MetricSelect/AddToExplorationsButton';
import { getDataSource, getTrailFor } from '../utils';
const ADD_TO_INVESTIGATION_MENU_TEXT = 'Add to investigation';
const ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT = 'investigations_divider'; // Text won't be visible
const ADD_TO_INVESTIGATION_MENU_GROUP_TEXT = 'Investigations';
interface PanelMenuState extends SceneObjectState {
body?: VizPanelMenu;
frame?: DataFrame;
labelName?: string;
fieldName?: string;
addExplorationsLink?: boolean;
explorationsButton?: AddToExplorationButton;
}
/**
* @todo the VizPanelMenu interface is overly restrictive, doesn't allow any member functions on this class, so everything is currently inlined
*/
export class PanelMenu extends SceneObjectBase<PanelMenuState> implements VizPanelMenu, SceneObject {
constructor(state: Partial<PanelMenuState>) {
super({ ...state, addExplorationsLink: state.addExplorationsLink ?? true });
this.addActivationHandler(() => {
let exploreUrl: Promise<string | undefined> | undefined;
try {
const viz = sceneGraph.getAncestor(this, VizPanel);
const queryRunner = getQueryRunnerFor(viz);
const queries = queryRunner?.state.queries ?? [];
queries.forEach((query) => {
// removing legendFormat to get verbose legend in Explore
delete query.legendFormat;
});
const trail = getTrailFor(this);
const dsValue = getDataSource(trail);
const timeRange = sceneGraph.getTimeRange(this);
exploreUrl = getExploreUrl({
queries,
dsRef: { uid: dsValue },
timeRange: timeRange.state.value,
scopedVars: { __sceneObject: { value: viz } },
});
} catch (e) {}
// Navigation options (all panels)
const items: PanelMenuItem[] = [
{
text: 'Navigation',
type: 'group',
},
{
text: 'Explore',
iconClassName: 'compass',
onClick: () => exploreUrl?.then((url) => url && window.open(url, '_blank')),
shortcut: 'p x',
},
];
this.setState({
body: new VizPanelMenu({
items,
}),
});
const addToExplorationsButton = new AddToExplorationButton({
labelName: this.state.labelName,
fieldName: this.state.fieldName,
frame: this.state.frame,
});
this._subs.add(
addToExplorationsButton?.subscribeToState(() => {
subscribeToAddToExploration(this);
})
);
this.setState({
explorationsButton: addToExplorationsButton,
});
if (this.state.addExplorationsLink) {
this.state.explorationsButton?.activate();
}
});
}
addItem(item: PanelMenuItem): void {
if (this.state.body) {
this.state.body.addItem(item);
}
}
setItems(items: PanelMenuItem[]): void {
if (this.state.body) {
this.state.body.setItems(items);
}
}
public static Component = ({ model }: SceneComponentProps<PanelMenu>) => {
const { body } = model.useState();
if (body) {
return <body.Component model={body} />;
}
return <></>;
};
}
const getInvestigationLink = (addToExplorations: AddToExplorationButton) => {
const links = getPluginLinkExtensions({
extensionPointId: extensionPointId,
context: addToExplorations.state.context,
});
return links.extensions[0];
};
const onAddToInvestigationClick = (event: React.MouseEvent, addToExplorations: AddToExplorationButton) => {
const link = getInvestigationLink(addToExplorations);
if (link && link.onClick) {
link.onClick(event);
}
};
function subscribeToAddToExploration(menu: PanelMenu) {
const addToExplorationButton = menu.state.explorationsButton;
if (addToExplorationButton) {
const link = getInvestigationLink(addToExplorationButton);
const existingMenuItems = menu.state.body?.state.items ?? [];
const existingAddToExplorationLink = existingMenuItems.find((item) => item.text === ADD_TO_INVESTIGATION_MENU_TEXT);
if (link) {
if (!existingAddToExplorationLink) {
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT,
type: 'divider',
});
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_GROUP_TEXT,
type: 'group',
});
menu.state.body?.addItem({
text: ADD_TO_INVESTIGATION_MENU_TEXT,
iconClassName: 'plus-square',
onClick: (e) => onAddToInvestigationClick(e, addToExplorationButton),
});
} else {
if (existingAddToExplorationLink) {
menu.state.body?.setItems(
existingMenuItems.filter(
(item) =>
[
ADD_TO_INVESTIGATION_MENU_DIVIDER_TEXT,
ADD_TO_INVESTIGATION_MENU_GROUP_TEXT,
ADD_TO_INVESTIGATION_MENU_TEXT,
].includes(item.text) === false
)
);
}
}
}
}
}