Files
grafana/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx
Gilles De Mey 8765c48389 Alerting: Remove legacy alerting (#83671)
Removes legacy alerting, so long and thanks for all the fish! 🐟

---------

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
Co-authored-by: Sonia Aguilar <soniaAguilarPeiron@users.noreply.github.com>
Co-authored-by: Armand Grillet <armandgrillet@users.noreply.github.com>
Co-authored-by: William Wernert <rwwiv@users.noreply.github.com>
Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
2024-03-14 15:36:35 +01:00

440 lines
14 KiB
TypeScript

import {
InterpolateFunction,
PanelMenuItem,
PanelPlugin,
PluginExtensionPanelContext,
PluginExtensionPoints,
getTimeZone,
urlUtil,
} from '@grafana/data';
import { config, getPluginLinkExtensions, locationService } from '@grafana/runtime';
import { LocalValueVariable, SceneGridRow, VizPanel, VizPanelMenu, sceneGraph } from '@grafana/scenes';
import { DataQuery, OptionsWithLegend } from '@grafana/schema';
import appEvents from 'app/core/app_events';
import { t } from 'app/core/internationalization';
import { scenesPanelToRuleFormValues } from 'app/features/alerting/unified/utils/rule-form';
import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils';
import { InspectTab } from 'app/features/inspector/types';
import { getScenePanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils';
import { addDataTrailPanelAction } from 'app/features/trails/dashboardIntegration';
import { ShowConfirmModalEvent } from 'app/types/events';
import { ShareModal } from '../sharing/ShareModal';
import { DashboardInteractions } from '../utils/interactions';
import { getEditPanelUrl, getInspectUrl, getViewPanelUrl, tryGetExploreUrlForPanel } from '../utils/urlBuilders';
import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils';
import { DashboardScene } from './DashboardScene';
import { LibraryVizPanel } from './LibraryVizPanel';
import { VizPanelLinks, VizPanelLinksMenu } from './PanelLinks';
import { UnlinkLibraryPanelModal } from './UnlinkLibraryPanelModal';
/**
* Behavior is called when VizPanelMenu is activated (ie when it's opened).
*/
export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) {
const asyncFunc = async () => {
// hm.. add another generic param to SceneObject to specify parent type?
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const panel = menu.parent as VizPanel;
const parent = panel.parent;
const plugin = panel.getPlugin();
const items: PanelMenuItem[] = [];
const moreSubMenu: PanelMenuItem[] = [];
const dashboard = getDashboardSceneFor(panel);
const { isEmbedded } = dashboard.state.meta;
const exploreMenuItem = await getExploreMenuItem(panel);
// For embedded dashboards we only have explore action for now
if (isEmbedded) {
if (exploreMenuItem) {
menu.setState({ items: [exploreMenuItem] });
}
return;
}
items.push({
text: t('panel.header-menu.view', `View`),
iconClassName: 'eye',
shortcut: 'v',
onClick: () => DashboardInteractions.panelMenuItemClicked('view'),
href: getViewPanelUrl(panel),
});
if (dashboard.canEditDashboard() && !isRepeat) {
// We could check isEditing here but I kind of think this should always be in the menu,
// and going into panel edit should make the dashboard go into edit mode is it's not already
items.push({
text: t('panel.header-menu.edit', `Edit`),
iconClassName: 'eye',
shortcut: 'e',
onClick: () => DashboardInteractions.panelMenuItemClicked('edit'),
href: getEditPanelUrl(getPanelIdForVizPanel(panel)),
});
}
items.push({
text: t('panel.header-menu.share', `Share`),
iconClassName: 'share-alt',
onClick: () => {
DashboardInteractions.panelMenuItemClicked('share');
dashboard.showModal(new ShareModal({ panelRef: panel.getRef(), dashboardRef: dashboard.getRef() }));
},
shortcut: 'p s',
});
if (dashboard.state.isEditing && !isRepeat) {
moreSubMenu.push({
text: t('panel.header-menu.duplicate', `Duplicate`),
onClick: () => {
DashboardInteractions.panelMenuItemClicked('duplicate');
dashboard.duplicatePanel(panel);
},
shortcut: 'p d',
});
}
moreSubMenu.push({
text: t('panel.header-menu.copy', `Copy`),
onClick: () => {
DashboardInteractions.panelMenuItemClicked('copy');
dashboard.copyPanel(panel);
},
});
if (dashboard.state.isEditing && !isRepeat) {
if (parent instanceof LibraryVizPanel) {
moreSubMenu.push({
text: t('panel.header-menu.unlink-library-panel', `Unlink library panel`),
onClick: () => {
DashboardInteractions.panelMenuItemClicked('unlinkLibraryPanel');
dashboard.showModal(
new UnlinkLibraryPanelModal({
panelRef: parent.getRef(),
})
);
},
});
} else {
moreSubMenu.push({
text: t('panel.header-menu.create-library-panel', `Create library panel`),
onClick: () => {
DashboardInteractions.panelMenuItemClicked('createLibraryPanel');
dashboard.showModal(
new ShareModal({
panelRef: panel.getRef(),
dashboardRef: dashboard.getRef(),
activeTab: shareDashboardType.libraryPanel,
})
);
},
});
}
}
moreSubMenu.push({
text: t('panel.header-menu.new-alert-rule', `New alert rule`),
onClick: (e) => onCreateAlert(panel),
});
if (hasLegendOptions(panel.state.options)) {
moreSubMenu.push({
text: panel.state.options.legend.showLegend
? t('panel.header-menu.hide-legend', 'Hide legend')
: t('panel.header-menu.show-legend', 'Show legend'),
onClick: (e) => {
e.preventDefault();
toggleVizPanelLegend(panel);
},
shortcut: 'p l',
});
}
if (dashboard.canEditDashboard() && plugin && !plugin.meta.skipDataQuery && !isRepeat) {
moreSubMenu.push({
text: t('panel.header-menu.get-help', 'Get help'),
onClick: (e: React.MouseEvent) => {
e.preventDefault();
onInspectPanel(panel, InspectTab.Help);
},
});
}
if (config.featureToggles.datatrails) {
addDataTrailPanelAction(dashboard, panel, items);
}
if (exploreMenuItem) {
items.push(exploreMenuItem);
}
items.push(getInspectMenuItem(plugin, panel, dashboard));
const { extensions } = getPluginLinkExtensions({
extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
context: createExtensionContext(panel, dashboard),
limitPerPlugin: 3,
});
if (extensions.length > 0 && !dashboard.state.isEditing) {
items.push({
text: 'Extensions',
iconClassName: 'plug',
type: 'submenu',
subMenu: createExtensionSubMenu(extensions),
});
}
if (moreSubMenu.length) {
items.push({
type: 'submenu',
text: t('panel.header-menu.more', `More...`),
iconClassName: 'cube',
subMenu: moreSubMenu,
onClick: (e) => {
e.preventDefault();
},
});
}
if (dashboard.state.isEditing && !isRepeat) {
items.push({
text: '',
type: 'divider',
});
items.push({
text: t('panel.header-menu.remove', `Remove`),
iconClassName: 'trash-alt',
onClick: () => {
DashboardInteractions.panelMenuItemClicked('remove');
onRemovePanel(dashboard, panel);
},
shortcut: 'p r',
});
}
menu.setState({ items });
};
asyncFunc();
}
export const repeatPanelMenuBehavior = (menu: VizPanelMenu) => panelMenuBehavior(menu, true);
async function getExploreMenuItem(panel: VizPanel): Promise<PanelMenuItem | undefined> {
const exploreUrl = await tryGetExploreUrlForPanel(panel);
if (!exploreUrl) {
return undefined;
}
return {
text: t('panel.header-menu.explore', `Explore`),
iconClassName: 'compass',
shortcut: 'p x',
onClick: () => DashboardInteractions.panelMenuItemClicked('explore'),
href: exploreUrl,
};
}
function getInspectMenuItem(
plugin: PanelPlugin | undefined,
panel: VizPanel,
dashboard: DashboardScene
): PanelMenuItem {
const inspectSubMenu: PanelMenuItem[] = [];
if (plugin && !plugin.meta.skipDataQuery) {
inspectSubMenu.push({
text: t('panel.header-menu.inspect-data', `Data`),
href: getInspectUrl(panel, InspectTab.Data),
onClick: (e) => {
e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data });
DashboardInteractions.panelMenuInspectClicked(InspectTab.Data);
},
});
if (dashboard instanceof DashboardScene && dashboard.state.meta.canEdit) {
inspectSubMenu.push({
text: t('panel.header-menu.query', `Query`),
href: getInspectUrl(panel, InspectTab.Query),
onClick: (e) => {
e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Query });
DashboardInteractions.panelMenuInspectClicked(InspectTab.Query);
},
});
}
}
inspectSubMenu.push({
text: t('panel.header-menu.inspect-json', `Panel JSON`),
href: getInspectUrl(panel, InspectTab.JSON),
onClick: (e) => {
e.preventDefault();
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.JSON });
DashboardInteractions.panelMenuInspectClicked(InspectTab.JSON);
},
});
return {
text: t('panel.header-menu.inspect', `Inspect`),
iconClassName: 'info-circle',
shortcut: 'i',
href: getInspectUrl(panel),
onClick: (e) => {
if (!e.isDefaultPrevented()) {
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data });
DashboardInteractions.panelMenuInspectClicked(InspectTab.Data);
}
},
subMenu: inspectSubMenu.length > 0 ? inspectSubMenu : undefined,
};
}
/**
* Behavior is called when VizPanelLinksMenu is activated (when it's opened).
*/
export function panelLinksBehavior(panelLinksMenu: VizPanelLinksMenu) {
if (!(panelLinksMenu.parent instanceof VizPanelLinks)) {
throw new Error('parent of VizPanelLinksMenu must be VizPanelLinks');
}
const panel = panelLinksMenu.parent.parent;
if (!(panel instanceof VizPanel)) {
throw new Error('parent of VizPanelLinks must be VizPanel');
}
panelLinksMenu.setState({ links: getPanelLinks(panel) });
}
export function getPanelLinks(panel: VizPanel) {
const interpolate: InterpolateFunction = (v, scopedVars) => {
return sceneGraph.interpolate(panel, v, scopedVars);
};
const linkSupplier = getScenePanelLinksSupplier(panel, interpolate);
if (!linkSupplier) {
return [];
}
const panelLinks = linkSupplier.getLinks(interpolate);
return panelLinks.map((panelLink) => ({
...panelLink,
onClick: (e: any, origin: any) => {
DashboardInteractions.panelLinkClicked({ has_multiple_links: panelLinks.length > 1 });
panelLink.onClick?.(e, origin);
},
}));
}
function createExtensionContext(panel: VizPanel, dashboard: DashboardScene): PluginExtensionPanelContext {
const timeRange = sceneGraph.getTimeRange(panel);
let queryRunner = getQueryRunnerFor(panel);
const targets: DataQuery[] = queryRunner?.state.queries as DataQuery[];
const id = getPanelIdForVizPanel(panel);
let scopedVars = {};
// Handle panel repeats scenario
if (panel.state.$variables) {
panel.state.$variables.state.variables.forEach((variable) => {
if (variable instanceof LocalValueVariable) {
scopedVars = {
...scopedVars,
[variable.state.name]: { value: variable.getValue(), text: variable.getValueText() },
};
}
});
}
// Handle row repeats scenario
if (panel.parent?.parent instanceof SceneGridRow) {
const row = panel.parent.parent;
if (row.state.$variables) {
row.state.$variables.state.variables.forEach((variable) => {
if (variable instanceof LocalValueVariable) {
scopedVars = {
...scopedVars,
[variable.state.name]: { value: variable.getValue(), text: variable.getValueText() },
};
}
});
}
}
return {
id,
pluginId: panel.state.pluginId,
title: panel.state.title,
timeRange: timeRange.state.value.raw,
timeZone: getTimeZone({
timeZone: timeRange.getTimeZone(),
}),
dashboard: {
uid: dashboard.state.uid!,
title: dashboard.state.title,
tags: dashboard.state.tags || [],
},
targets,
scopedVars,
data: queryRunner?.state.data,
};
}
export function onRemovePanel(dashboard: DashboardScene, panel: VizPanel) {
appEvents.publish(
new ShowConfirmModalEvent({
title: 'Remove panel',
text: 'Are you sure you want to remove this panel?',
icon: 'trash-alt',
yesText: 'Remove',
onConfirm: () => dashboard.removePanel(panel),
})
);
}
const onCreateAlert = async (panel: VizPanel) => {
DashboardInteractions.panelMenuItemClicked('create-alert');
const formValues = await scenesPanelToRuleFormValues(panel);
const ruleFormUrl = urlUtil.renderUrl('/alerting/new', {
defaults: JSON.stringify(formValues),
returnTo: location.pathname + location.search,
});
locationService.push(ruleFormUrl);
DashboardInteractions.panelMenuItemClicked('create-alert');
};
export function toggleVizPanelLegend(vizPanel: VizPanel): void {
const options = vizPanel.state.options;
if (hasLegendOptions(options) && typeof options.legend.showLegend === 'boolean') {
vizPanel.onOptionsChange({
legend: {
showLegend: options.legend.showLegend ? false : true,
},
});
}
DashboardInteractions.panelMenuItemClicked('toggleLegend');
}
function hasLegendOptions(optionsWithLegend: unknown): optionsWithLegend is OptionsWithLegend {
return optionsWithLegend != null && typeof optionsWithLegend === 'object' && 'legend' in optionsWithLegend;
}
const onInspectPanel = (vizPanel: VizPanel, tab?: InspectTab) => {
locationService.partial({
inspect: vizPanel.state.key,
inspectTab: tab,
});
DashboardInteractions.panelMenuInspectClicked(tab ?? InspectTab.Data);
};