DashboardScene: Add inspect submenu to panel menu (#78679)

* DashboardScene: Add inspect submenu to panel menu

* Test fix
This commit is contained in:
Dominik Prokop 2023-11-27 17:23:16 +01:00 committed by GitHub
parent 7a5f76d547
commit 5015b5b2b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 62 additions and 14 deletions

View File

@ -1,4 +1,4 @@
import { act, render, screen } from '@testing-library/react'; import { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import React from 'react'; import React from 'react';
import { TestProvider } from 'test/helpers/TestProvider'; import { TestProvider } from 'test/helpers/TestProvider';
@ -122,12 +122,8 @@ describe('DashboardScenePage', () => {
// Somethig with Dropdown that is not working inside react-testing // Somethig with Dropdown that is not working inside react-testing
await userEvent.click(screen.getByLabelText('Menu for panel with title Panel B')); await userEvent.click(screen.getByLabelText('Menu for panel with title Panel B'));
const inspectLink = (await screen.findByRole('link', { name: /Inspect/ })).getAttribute('href')!; const inspectMenuItem = await screen.findAllByText('Inspect');
act(() => locationService.push(inspectLink)); act(() => fireEvent.click(inspectMenuItem[0]));
// I get not implemented exception here (from navigation / js-dom).
// Mocking window.location.assign did not help
//await userEvent.click(await screen.findByRole('link', { name: /Inspect/ }));
expect(await screen.findByText('Inspect: Panel B')).toBeInTheDocument(); expect(await screen.findByText('Inspect: Panel B')).toBeInTheDocument();

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel } from '@grafana/scenes'; import { SceneComponentProps, SceneObjectBase, SceneObjectState, VizPanel, VizPanelMenu } from '@grafana/scenes';
import { PanelModel } from 'app/features/dashboard/state'; import { PanelModel } from 'app/features/dashboard/state';
import { getLibraryPanel } from 'app/features/library-panels/state/api'; import { getLibraryPanel } from 'app/features/library-panels/state/api';
import { createPanelDataProvider } from '../utils/createPanelDataProvider'; import { createPanelDataProvider } from '../utils/createPanelDataProvider';
import { panelMenuBehavior } from './PanelMenuBehavior';
interface LibraryVizPanelState extends SceneObjectState { interface LibraryVizPanelState extends SceneObjectState {
// Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it. // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it.
title: string; title: string;
@ -39,6 +41,9 @@ export class LibraryVizPanel extends SceneObjectBase<LibraryVizPanelState> {
pluginVersion: libPanelModel.pluginVersion, pluginVersion: libPanelModel.pluginVersion,
displayMode: libPanelModel.transparent ? 'transparent' : undefined, displayMode: libPanelModel.transparent ? 'transparent' : undefined,
$data: createPanelDataProvider(libPanelModel), $data: createPanelDataProvider(libPanelModel),
menu: new VizPanelMenu({
$behaviors: [panelMenuBehavior],
}),
}); });
} catch (err) { } catch (err) {
vizPanel.setState({ vizPanel.setState({

View File

@ -29,7 +29,7 @@ describe('panelMenuBehavior', () => {
it('Given standard panel', async () => { it('Given standard panel', async () => {
const { menu, panel } = await buildTestScene({}); const { menu, panel } = await buildTestScene({});
Object.assign(panel, 'getPlugin', () => getPanelPlugin({})); panel.getPlugin = () => getPanelPlugin({ skipDataQuery: false });
mocks.contextSrv.hasAccessToExplore.mockReturnValue(true); mocks.contextSrv.hasAccessToExplore.mockReturnValue(true);
mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore')); mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore'));
@ -56,6 +56,9 @@ describe('panelMenuBehavior', () => {
// verify inspect url keeps url params and adds inspect=<panel-key> // verify inspect url keeps url params and adds inspect=<panel-key>
expect(menu.state.items?.[4].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&inspect=panel-12'); expect(menu.state.items?.[4].href).toBe('/scenes/dashboard/dash-1?from=now-5m&to=now&inspect=panel-12');
expect(menu.state.items?.[4].subMenu).toBeDefined();
expect(menu.state.items?.[4].subMenu?.length).toBe(3);
}); });
}); });

View File

@ -23,9 +23,12 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
// hm.. add another generic param to SceneObject to specify parent type? // hm.. add another generic param to SceneObject to specify parent type?
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const panel = menu.parent as VizPanel; const panel = menu.parent as VizPanel;
const plugin = panel.getPlugin();
const location = locationService.getLocation(); const location = locationService.getLocation();
const items: PanelMenuItem[] = []; const items: PanelMenuItem[] = [];
const moreSubMenu: PanelMenuItem[] = []; const moreSubMenu: PanelMenuItem[] = [];
const inspectSubMenu: PanelMenuItem[] = [];
const panelId = getPanelIdForVizPanel(panel); const panelId = getPanelIdForVizPanel(panel);
const dashboard = panel.getRoot(); const dashboard = panel.getRoot();
@ -65,8 +68,8 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
shortcut: 'p s', shortcut: 'p s',
}); });
if (panel instanceof LibraryVizPanel) { if (panel.parent instanceof LibraryVizPanel) {
// TODO: Implement unlinking library panel // TODO: Implement lib panel unlinking
} else { } else {
moreSubMenu.push({ moreSubMenu.push({
text: t('panel.header-menu.create-library-panel', `Create library panel`), text: t('panel.header-menu.create-library-panel', `Create library panel`),
@ -100,12 +103,52 @@ export function panelMenuBehavior(menu: VizPanelMenu) {
}); });
} }
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 });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: 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 });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: 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 });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.JSON });
},
});
items.push({ items.push({
text: t('panel.header-menu.inspect', `Inspect`), text: t('panel.header-menu.inspect', `Inspect`),
iconClassName: 'info-circle', iconClassName: 'info-circle',
shortcut: 'i', shortcut: 'i',
onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data }),
href: getInspectUrl(panel), href: getInspectUrl(panel),
onClick: (e) => {
if (!e.isDefaultPrevented()) {
locationService.partial({ inspect: panel.state.key, inspectTab: InspectTab.Data });
reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data });
}
},
subMenu: inspectSubMenu.length > 0 ? inspectSubMenu : undefined,
}); });
if (moreSubMenu.length) { if (moreSubMenu.length) {

View File

@ -3,6 +3,7 @@ import { config, locationSearchToObject, locationService } from '@grafana/runtim
import { sceneGraph, VizPanel } from '@grafana/scenes'; import { sceneGraph, VizPanel } from '@grafana/scenes';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
import { getExploreUrl } from 'app/core/utils/explore'; import { getExploreUrl } from 'app/core/utils/explore';
import { InspectTab } from 'app/features/inspector/types';
import { getQueryRunnerFor } from './utils'; import { getQueryRunnerFor } from './utils';
@ -71,8 +72,8 @@ export function getViewPanelUrl(vizPanel: VizPanel) {
return locationUtil.getUrlForPartial(locationService.getLocation(), { viewPanel: vizPanel.state.key }); return locationUtil.getUrlForPartial(locationService.getLocation(), { viewPanel: vizPanel.state.key });
} }
export function getInspectUrl(vizPanel: VizPanel) { export function getInspectUrl(vizPanel: VizPanel, inspectTab?: InspectTab) {
return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect: vizPanel.state.key }); return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect: vizPanel.state.key, inspectTab });
} }
export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise<string | undefined> { export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise<string | undefined> {