From 544582e495079d4766956fd6c77fe89cd437f6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ida=20=C5=A0tambuk?= Date: Mon, 15 Sep 2025 20:54:43 +0200 Subject: [PATCH] Dynamic Dashboards: Add tracking for item actions (#111059) --- .../src/selectors/components.ts | 3 + .../edit-pane/EditPaneHeader.test.tsx | 93 ++++++++++ .../edit-pane/EditPaneHeader.tsx | 12 +- .../CanvasGridAddActions.test.tsx | 159 ++++++++++++++++++ .../layouts-shared/CanvasGridAddActions.tsx | 13 +- .../dashboard-scene/utils/interactions.ts | 25 +++ .../DashboardRow/DashboardRow.test.tsx | 15 ++ .../components/DashboardRow/DashboardRow.tsx | 6 +- .../app/features/dashboard/utils/tracking.ts | 14 ++ 9 files changed, 337 insertions(+), 3 deletions(-) create mode 100644 public/app/features/dashboard-scene/edit-pane/EditPaneHeader.test.tsx create mode 100644 public/app/features/dashboard-scene/scene/layouts-shared/CanvasGridAddActions.test.tsx diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index ecf551d4395..05b0f553578 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -45,6 +45,9 @@ export const versionedComponents = { pasteTab: { '12.1.0': 'data-testid CanvasGridAddActions paste-tab', }, + pastePanel: { + '12.1.0': 'data-testid CanvasGridAddActions paste-panel', + }, }, DashboardEditPaneSplitter: { primaryBody: { diff --git a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.test.tsx b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.test.tsx new file mode 100644 index 00000000000..627ceb96d3b --- /dev/null +++ b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.test.tsx @@ -0,0 +1,93 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { selectors } from '@grafana/e2e-selectors'; +import { SceneTimeRange } from '@grafana/scenes'; + +import { DashboardScene } from '../scene/DashboardScene'; +import { RowItem } from '../scene/layout-rows/RowItem'; +import { RowsLayoutManager } from '../scene/layout-rows/RowsLayoutManager'; +import { TabItem } from '../scene/layout-tabs/TabItem'; +import { TabsLayoutManager } from '../scene/layout-tabs/TabsLayoutManager'; +import { DashboardInteractions } from '../utils/interactions'; +import { activateFullSceneTree } from '../utils/test-utils'; + +import { DashboardEditPane } from './DashboardEditPane'; +import { EditPaneHeader } from './EditPaneHeader'; +import { ElementSelection } from './ElementSelection'; + +// Mock DashboardInteractions +jest.mock('../utils/interactions', () => ({ + DashboardInteractions: { + trackRemoveRowClick: jest.fn(), + trackRemoveTabClick: jest.fn(), + }, +})); + +const sceneWithTab = new DashboardScene({ + $timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }), + isEditing: true, + body: new TabsLayoutManager({ + tabs: [ + new TabItem({ + title: 'test tab', + }), + ], + }), +}); + +const sceneWithRow = new DashboardScene({ + $timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }), + isEditing: true, + body: new RowsLayoutManager({ + rows: [ + new RowItem({ + title: 'test row', + }), + ], + }), +}); + +const buildTestScene = (scene: DashboardScene) => { + activateFullSceneTree(scene); + return scene; +}; + +describe('EditPaneHeader', () => { + const mockEditPane = { + state: { selection: null }, + clearSelection: jest.fn(), + } as unknown as DashboardEditPane; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('tracking item deletion', () => { + it('should call DashboardActions.trackDeleteRow when deleting a row', async () => { + const user = userEvent.setup(); + const scene = buildTestScene(sceneWithRow); + const row = (scene.state.body as RowsLayoutManager).state.rows[0]; + const elementSelection = new ElementSelection([['row-test', row.getRef()]]); + const editableElement = elementSelection.createSelectionElement()!; + + render(); + + await user.click(screen.getByTestId(selectors.components.EditPaneHeader.deleteButton)); + expect(DashboardInteractions.trackRemoveRowClick).toHaveBeenCalled(); + }); + + it('should call DashboardActions.trackDeleteTab when deleting a tab', async () => { + const user = userEvent.setup(); + const scene = buildTestScene(sceneWithTab); + const tab = (scene.state.body as TabsLayoutManager).state.tabs[0]; + const elementSelection = new ElementSelection([['tab-test', tab.getRef()]]); + const editableElement = elementSelection.createSelectionElement()!; + + render(); + + await user.click(screen.getByTestId(selectors.components.EditPaneHeader.deleteButton)); + expect(DashboardInteractions.trackRemoveTabClick).toHaveBeenCalled(); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx index a98674d84d0..e0e5a74a10a 100644 --- a/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx +++ b/public/app/features/dashboard-scene/edit-pane/EditPaneHeader.tsx @@ -4,6 +4,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { Button, Menu, Stack, Text, useStyles2, Dropdown, Icon, IconButton } from '@grafana/ui'; +import { trackDeleteDashboardElement } from 'app/features/dashboard/utils/tracking'; import { EditableDashboardElement } from '../scene/types/EditableDashboardElement'; @@ -26,6 +27,15 @@ export function EditPaneHeader({ element, editPane }: EditPaneHeaderProps) { const onGoBack = () => editPane.clearSelection(); const canGoBack = editPane.state.selection; + const onDeleteElement = () => { + if (onConfirmDelete) { + onConfirmDelete(); + } else if (onDelete) { + onDelete(); + } + trackDeleteDashboardElement(elementInfo); + }; + return (
@@ -75,7 +85,7 @@ export function EditPaneHeader({ element, editPane }: EditPaneHeaderProps) { {(onDelete || onConfirmDelete) && ( @@ -41,15 +45,19 @@ export function CanvasGridAddActions({ layoutManager }: Props) { { addNewRowTo(layoutManager); + DashboardInteractions.trackGroupRowClick(); }} > { addNewTabTo(layoutManager); + DashboardInteractions.trackGroupTabClick(); }} > @@ -67,11 +75,13 @@ export function CanvasGridAddActions({ layoutManager }: Props) { {renderUngroupAction(layoutManager)} {hasCopiedPanel && layoutManager.pastePanel && (