This commit is contained in:
idastambuk
2026-01-14 17:33:24 +01:00
parent f56ce2da88
commit 48579dc946
8 changed files with 128 additions and 3 deletions
@@ -1,5 +1,7 @@
import { test, expect } from '@grafana/plugin-e2e';
import { addNewPanelFromSidebar } from './utils';
test.use({
featureToggles: {
kubernetesDashboards: true,
@@ -44,5 +46,94 @@ test.describe(
.click();
await expect(dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.General.content)).toBeVisible();
});
test('can add a panel from the sidebar on a new dashboard', async ({ gotoDashboardPage, selectors, page }) => {
const dashboardPage = await gotoDashboardPage({});
// check that the sidebar is open on Add section
expect(await dashboardPage.getByGrafanaSelector(selectors.components.Sidebar.newPanelButton)).toBeVisible();
await dashboardPage.getByGrafanaSelector(selectors.components.Sidebar.newPanelButton).click();
// check that new panel has been added
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel'))
).toBeVisible();
addNewPanelFromSidebar(dashboardPage, selectors);
// check that another has been added
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel'))
).toHaveCount(2);
});
test('adds a new panel from the sidebar into the layout that was selected last', async ({
gotoDashboardPage,
selectors,
page,
}) => {
const dashboardPage = await gotoDashboardPage({});
await dashboardPage.getByGrafanaSelector(selectors.components.Sidebar.newPanelButton).click();
// group into tab
await dashboardPage.getByGrafanaSelector(selectors.components.CanvasGridAddActions.groupPanels).click();
await page.getByText('Group into tab').click();
// add new panel from the sidebar
addNewPanelFromSidebar(dashboardPage, selectors);
// check that another panel has been added inside the tab
const tab = dashboardPage.getByGrafanaSelector(selectors.components.LayoutContainer('tab New tab'));
await expect(tab.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(2);
// add new tab
await dashboardPage.getByGrafanaSelector(selectors.components.CanvasGridAddActions.addTab).click();
// add new panel from the sidebar
addNewPanelFromSidebar(dashboardPage, selectors);
//check that new panel has been added there
const tab2 = dashboardPage.getByGrafanaSelector(selectors.components.LayoutContainer('tab New tab 1'));
await expect(tab2.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(1);
// panel is selected
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldInput('Title'))
).toBeVisible();
addNewPanelFromSidebar(dashboardPage, selectors);
await expect(tab2.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(2);
// group into row
await dashboardPage.getByGrafanaSelector(selectors.components.CanvasGridAddActions.groupPanels).click();
await page.getByText('Group into row').click();
// add into the row
addNewPanelFromSidebar(dashboardPage, selectors);
const row = dashboardPage.getByGrafanaSelector(selectors.components.LayoutContainer('row New row'));
// scroll to the bottom of the row to load all panels
const scrollContainer = page
.getByTestId(selectors.components.DashboardEditPaneSplitter.primaryBody)
.locator('> div')
.first();
await scrollContainer.evaluate((el) => el.scrollTo(0, el.scrollHeight));
await expect(row.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(3);
// add new row and add into it
await dashboardPage.getByGrafanaSelector(selectors.components.CanvasGridAddActions.addRow).click();
addNewPanelFromSidebar(dashboardPage, selectors);
const row1 = dashboardPage.getByGrafanaSelector(selectors.components.LayoutContainer('row New row 1'));
await expect(row1.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(1);
// panel is selected
await expect(
dashboardPage.getByGrafanaSelector(selectors.components.PanelEditor.OptionsPane.fieldInput('Title'))
).toBeVisible();
addNewPanelFromSidebar(dashboardPage, selectors);
await scrollContainer.evaluate((el) => el.scrollTo(0, el.scrollHeight));
// check that the new panel is added next to the last panel selected
await expect(row1.getByTestId(selectors.components.Panels.Panel.title('New panel'))).toHaveCount(2);
});
}
);
// utils
// add new panel through sidebar
@@ -249,3 +249,8 @@ export async function switchToAutoGrid(page: Page, dashboardPage: DashboardPage)
await confirmModal.click();
}
}
export async function addNewPanelFromSidebar(dashboardPage: DashboardPage, selectors: E2ESelectorGroups) {
await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.addButton).click();
await dashboardPage.getByGrafanaSelector(selectors.components.Sidebar.newPanelButton).click();
}
@@ -64,6 +64,9 @@ export const versionedComponents = {
dockToggle: {
'12.4.0': 'data-testid sidebar-dock-toggle',
},
newPanelButton: {
'12.4.0': 'data-testid sidebar add new panel',
},
},
EditPaneHeader: {
deleteButton: {
@@ -79,6 +82,9 @@ export const versionedComponents = {
'12.1.0': 'data-testid EditPaneHeader duplicate',
},
},
LayoutContainer: {
'12.4.0': (identifier: string) => `data-testid Layout container ${identifier}`,
},
TimePicker: {
openButton: {
[MIN_GRAFANA_VERSION]: 'data-testid TimePicker Open Button',
@@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { SceneObject } from '@grafana/scenes';
import { Sidebar, Text, useStyles2 } from '@grafana/ui';
@@ -30,6 +31,7 @@ export function DashboardSidePaneNew({ onAddPanel, dashboard }: { onAddPanel: ()
return (
<div
role="button"
data-testid={selectors.components.Sidebar.newPanelButton}
tabIndex={0}
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
@@ -9,12 +9,10 @@ import { getNavModel } from 'app/core/selectors/navModel';
import { useSelector } from 'app/types/store';
import { DashboardEditPaneSplitter } from '../edit-pane/DashboardEditPaneSplitter';
import { isEmptyDashboard } from '../saving/DashboardPrompt';
import { DashboardScene } from './DashboardScene';
import { PanelSearchLayout } from './PanelSearchLayout';
import { SoloPanelContextProvider, useDefineSoloPanelContext } from './SoloPanelContext';
import { getDashboardSceneFor } from '../utils/utils';
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
const {
@@ -1,10 +1,12 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { SceneComponentProps, sceneGraph } from '@grafana/scenes';
import { useStyles2 } from '@grafana/ui';
import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { getTestIdForLayout } from '../../utils/test-utils';
import { useDashboardState } from '../../utils/utils';
import { useSoloPanelContext } from '../SoloPanelContext';
import { CanvasGridAddActions } from '../layouts-shared/CanvasGridAddActions';
@@ -33,6 +35,7 @@ export function AutoGridLayoutRenderer({ model }: SceneComponentProps<AutoGridLa
return (
<div
data-testid={selectors.components.LayoutContainer(getTestIdForLayout(model))}
className={cx(styles.container, fillScreen && styles.containerFillScreen, isEditing && styles.containerEditing)}
ref={model.containerRef}
>
@@ -1,6 +1,7 @@
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import {
@@ -31,6 +32,7 @@ import {
import { serializeDefaultGridLayout } from '../../serialization/layoutSerializers/DefaultGridLayoutSerializer';
import { isRepeatCloneOrChildOf } from '../../utils/clone';
import { dashboardSceneGraph } from '../../utils/dashboardSceneGraph';
import { getTestIdForLayout } from '../../utils/test-utils';
import {
forceRenderChildren,
getPanelIdForVizPanel,
@@ -657,7 +659,10 @@ function DefaultGridLayoutManagerRenderer({ model }: SceneComponentProps<Default
}
return (
<div className={cx(styles.container, isEditing && styles.containerEditing)}>
<div
className={cx(styles.container, isEditing && styles.containerEditing)}
data-testid={selectors.components.LayoutContainer(getTestIdForLayout(model))}
>
{model.state.grid.Component && <model.state.grid.Component model={model.state.grid} />}
{showCanvasActions && (
<div className={styles.actionsWrapper}>
@@ -23,12 +23,18 @@ import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/co
import { DashboardDTO } from 'app/types/dashboard';
import { VizPanelLinks, VizPanelLinksMenu } from '../scene/PanelLinks';
import { AutoGridLayout } from '../scene/layout-auto-grid/AutoGridLayout';
import { DashboardGridItem, RepeatDirection } from '../scene/layout-default/DashboardGridItem';
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior';
import { RowItem } from '../scene/layout-rows/RowItem';
import { TabItem } from '../scene/layout-tabs/TabItem';
import { DashboardLayoutGrid } from '../scene/types/DashboardLayoutGrid';
import { transformSaveModelSchemaV2ToScene } from '../serialization/transformSaveModelSchemaV2ToScene';
import { transformSceneToSaveModelSchemaV2 } from '../serialization/transformSceneToSaveModelSchemaV2';
import { getRowOrTabForSceneObject } from './utils';
export function setupLoadDashboardMock(rsp: DeepPartial<DashboardDTO>, spy?: jest.Mock) {
const loadDashboardMock = (spy || jest.fn()).mockResolvedValue(rsp);
const loadSnapshotMock = (spy || jest.fn()).mockResolvedValue(rsp);
@@ -311,3 +317,12 @@ export function getTestDashboardSceneFromSaveModel(spec?: Partial<DashboardV2Spe
return dashboard;
}
// returns e.g. data-testid Layout container row Row title
export function getTestIdForLayout(model: AutoGridLayout | DashboardLayoutGrid) {
const parentRowOrTab = getRowOrTabForSceneObject(model);
const parentType = parentRowOrTab instanceof TabItem ? 'tab' : parentRowOrTab instanceof RowItem ? 'row' : '';
const parentTitle =
parentRowOrTab instanceof TabItem || parentRowOrTab instanceof RowItem ? parentRowOrTab.state.title : '';
return `${parentType} ${parentTitle}`;
}