From 5015b5b2b052e6f9c128311dc0415a22e8227386 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 27 Nov 2023 17:23:16 +0100 Subject: [PATCH] DashboardScene: Add inspect submenu to panel menu (#78679) * DashboardScene: Add inspect submenu to panel menu * Test fix --- .../pages/DashboardScenePage.test.tsx | 10 ++-- .../dashboard-scene/scene/LibraryVizPanel.tsx | 7 ++- .../scene/PanelMenuBehavior.test.tsx | 5 +- .../scene/PanelMenuBehavior.tsx | 49 +++++++++++++++++-- .../dashboard-scene/utils/urlBuilders.ts | 5 +- 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index debaf3a0cbf..4553af1f4d8 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -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 React from 'react'; import { TestProvider } from 'test/helpers/TestProvider'; @@ -122,12 +122,8 @@ describe('DashboardScenePage', () => { // Somethig with Dropdown that is not working inside react-testing await userEvent.click(screen.getByLabelText('Menu for panel with title Panel B')); - const inspectLink = (await screen.findByRole('link', { name: /Inspect/ })).getAttribute('href')!; - act(() => locationService.push(inspectLink)); - - // 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/ })); + const inspectMenuItem = await screen.findAllByText('Inspect'); + act(() => fireEvent.click(inspectMenuItem[0])); expect(await screen.findByText('Inspect: Panel B')).toBeInTheDocument(); diff --git a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx index ce09b6269f6..5cc0d63a24c 100644 --- a/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx +++ b/public/app/features/dashboard-scene/scene/LibraryVizPanel.tsx @@ -1,11 +1,13 @@ 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 { getLibraryPanel } from 'app/features/library-panels/state/api'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; +import { panelMenuBehavior } from './PanelMenuBehavior'; + interface LibraryVizPanelState extends SceneObjectState { // Library panels use title from dashboard JSON's panel model, not from library panel definition, hence we pass it. title: string; @@ -39,6 +41,9 @@ export class LibraryVizPanel extends SceneObjectBase { pluginVersion: libPanelModel.pluginVersion, displayMode: libPanelModel.transparent ? 'transparent' : undefined, $data: createPanelDataProvider(libPanelModel), + menu: new VizPanelMenu({ + $behaviors: [panelMenuBehavior], + }), }); } catch (err) { vizPanel.setState({ diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx index 838b7d948a2..53f3aa79bc9 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.test.tsx @@ -29,7 +29,7 @@ describe('panelMenuBehavior', () => { it('Given standard panel', async () => { const { menu, panel } = await buildTestScene({}); - Object.assign(panel, 'getPlugin', () => getPanelPlugin({})); + panel.getPlugin = () => getPanelPlugin({ skipDataQuery: false }); mocks.contextSrv.hasAccessToExplore.mockReturnValue(true); mocks.getExploreUrl.mockReturnValue(Promise.resolve('/explore')); @@ -56,6 +56,9 @@ describe('panelMenuBehavior', () => { // verify inspect url keeps url params and adds inspect= 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); }); }); diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index 7029a5faf47..d1902cb4da3 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -23,9 +23,12 @@ export function panelMenuBehavior(menu: VizPanelMenu) { // 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 plugin = panel.getPlugin(); + const location = locationService.getLocation(); const items: PanelMenuItem[] = []; const moreSubMenu: PanelMenuItem[] = []; + const inspectSubMenu: PanelMenuItem[] = []; const panelId = getPanelIdForVizPanel(panel); const dashboard = panel.getRoot(); @@ -65,8 +68,8 @@ export function panelMenuBehavior(menu: VizPanelMenu) { shortcut: 'p s', }); - if (panel instanceof LibraryVizPanel) { - // TODO: Implement unlinking library panel + if (panel.parent instanceof LibraryVizPanel) { + // TODO: Implement lib panel unlinking } else { moreSubMenu.push({ 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({ text: t('panel.header-menu.inspect', `Inspect`), iconClassName: 'info-circle', shortcut: 'i', - onClick: () => reportInteraction('dashboards_panelheader_menu', { item: 'inspect', tab: InspectTab.Data }), 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) { diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index 0dad547cca7..6c67abbc33d 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -3,6 +3,7 @@ import { config, locationSearchToObject, locationService } from '@grafana/runtim import { sceneGraph, VizPanel } from '@grafana/scenes'; import { contextSrv } from 'app/core/core'; import { getExploreUrl } from 'app/core/utils/explore'; +import { InspectTab } from 'app/features/inspector/types'; import { getQueryRunnerFor } from './utils'; @@ -71,8 +72,8 @@ export function getViewPanelUrl(vizPanel: VizPanel) { return locationUtil.getUrlForPartial(locationService.getLocation(), { viewPanel: vizPanel.state.key }); } -export function getInspectUrl(vizPanel: VizPanel) { - return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect: vizPanel.state.key }); +export function getInspectUrl(vizPanel: VizPanel, inspectTab?: InspectTab) { + return locationUtil.getUrlForPartial(locationService.getLocation(), { inspect: vizPanel.state.key, inspectTab }); } export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise {