mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 16:10:43 +00:00
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com> Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
363 lines
13 KiB
JavaScript
363 lines
13 KiB
JavaScript
//dashboard panel edit page
|
|
// Methods: Duplicate panel, Edit panel, Delete panel, Download json, Download csv, Move to another tab, Fullscreen panel, Refresh panel, Edit layout, Go to logs, Create alert from panel
|
|
import { expect } from '@playwright/test';
|
|
|
|
export default class DashboardPanel {
|
|
constructor(page) {
|
|
this.page = page;
|
|
|
|
this.editArrow = page.locator(
|
|
'[data-test="dashboard-edit-panel-area-test-dropdown"]'
|
|
);
|
|
this.duplicate = page.locator('[data-test="dashboard-duplicate-panel"]');
|
|
this.dashboardsMenuItem = page.locator(
|
|
'[data-test="menu-link-/dashboards-item"]'
|
|
);
|
|
this.applyBtn = page.locator('[data-test="dashboard-apply"]');
|
|
this.namepanel = page.locator('[data-test="dashboard-panel-name"]');
|
|
this.saveBtn = page.locator('[data-test="dashboard-panel-save"]');
|
|
this.edit = page.locator('[data-test="dashboard-edit-panel"]');
|
|
this.delete = page.locator('[data-test="dashboard-delete-panel"]');
|
|
this.downloadJson = page.locator(
|
|
'[data-test="dashboard-panel-download-as-json-btn"]'
|
|
);
|
|
this.downloadCsv = page.locator(
|
|
'[data-test="dashboard-panel-download-as-csv-btn"]'
|
|
);
|
|
this.moveTab = page.locator(
|
|
'[data-test="dashboard-move-to-another-panel"]'
|
|
);
|
|
this.deleteConfirmBtn = page.locator(
|
|
'[data-test="confirm-dialog"] [data-test="o-dialog-primary-btn"]'
|
|
);
|
|
this.fullscreen = page.locator(
|
|
'[data-test="dashboard-panel-fullscreen-btn"]'
|
|
);
|
|
this.refreshBtn = page.locator(
|
|
'[data-test="dashboard-panel-refresh-panel-btn"]'
|
|
);
|
|
this.editLayout = page.locator('[data-test="dashboard-edit-layout"]');
|
|
this.layoutEditBtn = page.locator('[data-test="dashboard-edit-btn"]');
|
|
this.layoutEditSaveBtn = page.locator('[data-test="dashboard-edit-save-btn"]');
|
|
this.panelHeight = page.locator(
|
|
'[data-test="panel-layout-settings-height-input-field"]'
|
|
);
|
|
this.saveLayout = page.locator(
|
|
'[data-test="panel-layout-settings-drawer"] [data-test="o-dialog-primary-btn"]'
|
|
);
|
|
this.cancelLayout = page.locator(
|
|
'[data-test="panel-layout-settings-cancel"]'
|
|
);
|
|
this.goToLogs = page.locator('[data-test="dashboard-move-to-logs-module"]');
|
|
this.queryInspector = page.locator(
|
|
'[data-test="dashboard-query-inspector-panel"]'
|
|
);
|
|
// In-editor "data view" query inspector (opened from the panel data view,
|
|
// distinct from the panel-dropdown Inspector above).
|
|
this.dataViewQueryInspectorBtn = page.locator(
|
|
'[data-test="dashboard-panel-data-view-query-inspector-btn"]'
|
|
);
|
|
this.inspectorQueryEditor = page.locator('.inspector-query-editor');
|
|
this.queryInspectorCloseBtn = page.locator(
|
|
'[data-test="query-inspector-dialog"] [data-test="o-dialog-close-btn"]'
|
|
);
|
|
|
|
// VERIFIED: Create Alert from Panel Menu (PanelContainer.vue:289)
|
|
this.createAlertFromPanel = page.locator('[data-test="dashboard-create-alert-from-panel"]');
|
|
|
|
// VERIFIED: Alert Context Menu selectors (AlertContextMenu.vue)
|
|
this.alertContextMenu = page.locator('[data-test="alert-context-menu"]');
|
|
this.alertContextMenuAbove = page.locator('[data-test="alert-context-menu-above"]');
|
|
this.alertContextMenuBelow = page.locator('[data-test="alert-context-menu-below"]');
|
|
|
|
// VERIFIED: Chart renderer (ChartRenderer.vue:19)
|
|
this.chartRendererCanvas = page.locator('[data-test="chart-renderer"]');
|
|
// VERIFIED: no-data empty-state overlay (PanelSchemaRenderer.vue) — same
|
|
// locator dashboard-panel-actions.js exposes as noDataElement/getNoDataLocator().
|
|
this.noDataElement = page.locator('[data-test="no-data"]');
|
|
}
|
|
|
|
// Click the in-editor "data view" query inspector button
|
|
async clickDataViewQueryInspector() {
|
|
await this.dataViewQueryInspectorBtn.click();
|
|
}
|
|
|
|
// Wait for the query inspector to be open AND its query rows rendered. The
|
|
// dialog becomes visible before the rows are populated, so waiting on the
|
|
// dialog alone lets an assertion run against an empty body.
|
|
async waitForQueryInspector(index = 0) {
|
|
await this.page
|
|
.locator('[data-test="query-inspector-dialog"]')
|
|
.first()
|
|
.waitFor({ state: "visible", timeout: 15000 });
|
|
await this.getExecutedQuery(index).waitFor({
|
|
state: "visible",
|
|
timeout: 15000,
|
|
});
|
|
}
|
|
|
|
// Query inspector editor content locator (chain .filter()/.last() as needed)
|
|
getInspectorQueryEditor() {
|
|
return this.inspectorQueryEditor;
|
|
}
|
|
|
|
// Executed-query row in the query inspector (by index)
|
|
getExecutedQuery(index = 0) {
|
|
return this.page.locator(`[data-test="query-inspector-executed-query-${index}"]`);
|
|
}
|
|
|
|
// Original-query row in the query inspector (by index)
|
|
getOriginalQuery(index = 0) {
|
|
return this.page.locator(`[data-test="query-inspector-original-query-${index}"]`);
|
|
}
|
|
|
|
// Duplicate panel
|
|
async duplicatePanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.duplicate.waitFor({ state: "visible" });
|
|
await this.duplicate.click();
|
|
}
|
|
|
|
//Edit panel
|
|
|
|
async editPanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.edit.waitFor({ state: "visible" });
|
|
await this.edit.click();
|
|
}
|
|
|
|
//Delete panel
|
|
async deletePanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.delete.waitFor({ state: "visible" });
|
|
await this.delete.click();
|
|
await this.deleteConfirmBtn.waitFor({ state: "visible" });
|
|
await this.deleteConfirmBtn.click();
|
|
}
|
|
|
|
//Download json
|
|
async downloadJsonPanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.downloadJson.waitFor({ state: "visible" });
|
|
await this.downloadJson.click();
|
|
}
|
|
|
|
//Download csv
|
|
async downloadCsvPanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.downloadCsv.waitFor({ state: "visible" });
|
|
await this.downloadCsv.click();
|
|
}
|
|
|
|
//move to another tab
|
|
async movePanelToAnotherTab() {
|
|
await this.editArrow.click();
|
|
await this.moveTab.waitFor({ state: "visible" });
|
|
await this.moveTab.click();
|
|
}
|
|
|
|
//fullscreen panel
|
|
async fullscreenPanel() {
|
|
await this.page.locator('[data-test="dashboard-panel-container"]').hover();
|
|
await this.fullscreen.waitFor({ state: "visible" });
|
|
await this.fullscreen.click();
|
|
await this.page.waitForSelector(
|
|
'[data-test="dashboard-viewpanel-close-btn"]',
|
|
{ state: "visible" }
|
|
);
|
|
await this.page
|
|
.locator('[data-test="dashboard-viewpanel-close-btn"]')
|
|
.click();
|
|
}
|
|
|
|
//refresh panel
|
|
async refreshPanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.refreshBtn.waitFor({ state: "visible" });
|
|
await this.refreshBtn.click();
|
|
}
|
|
|
|
//edit layout
|
|
async editLayoutPanel(panelName, height) {
|
|
await this.layoutEditBtn.waitFor({ state: "visible" });
|
|
await this.layoutEditBtn.click();
|
|
const dropdownBtn = this.page.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`);
|
|
await dropdownBtn.waitFor({ state: "visible" });
|
|
await dropdownBtn.click();
|
|
await this.editLayout.waitFor({ state: "visible" });
|
|
await this.editLayout.click();
|
|
await this.panelHeight.waitFor({ state: "visible" });
|
|
await this.panelHeight.click();
|
|
await this.panelHeight.fill(height);
|
|
await this.saveLayout.click();
|
|
await expect(this.layoutEditSaveBtn).toBeEnabled();
|
|
await this.layoutEditSaveBtn.click();
|
|
await this.layoutEditSaveBtn.waitFor({ state: "hidden" });
|
|
}
|
|
|
|
//Edit Panel: Go to logs
|
|
async goToLogsPanel(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.goToLogs.waitFor({ state: "visible" });
|
|
await this.goToLogs.click();
|
|
}
|
|
|
|
// Create Alert from panel dropdown menu
|
|
async createAlertFromPanelMenu(panelName) {
|
|
await this.page
|
|
.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`)
|
|
.click();
|
|
await this.createAlertFromPanel.waitFor({ state: "visible" });
|
|
await this.createAlertFromPanel.click();
|
|
}
|
|
|
|
// Right-click on chart center to open alert context menu
|
|
// Retries because ECharts needs time after data load to register contextmenu handlers
|
|
async rightClickChartForAlert(maxRetries = 3) {
|
|
const { chartCanvas, position } = await this.getChartRightClickTarget();
|
|
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
await chartCanvas.click({ button: "right", position });
|
|
const menuVisible = await this.alertContextMenu
|
|
.waitFor({ state: "visible", timeout: 3000 })
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
if (menuVisible) return;
|
|
}
|
|
// Final attempt without catching - let it throw if still not visible
|
|
await chartCanvas.click({ button: "right", position });
|
|
}
|
|
|
|
// Single right-click with no menu retry, for series types that must not open the menu.
|
|
async rightClickChart() {
|
|
const { chartCanvas, position } = await this.getChartRightClickTarget();
|
|
await chartCanvas.click({ button: "right", position });
|
|
}
|
|
|
|
async getChartRightClickTarget() {
|
|
const chartCanvas = this.chartRendererCanvas.first();
|
|
await chartCanvas.waitFor({ state: "visible", timeout: 15000 });
|
|
|
|
// After savePanel() a same-sized no-data overlay intercepts clicks until the re-query renders.
|
|
await this.noDataElement
|
|
.first()
|
|
.waitFor({ state: "hidden", timeout: 20000 })
|
|
.catch(() => {});
|
|
|
|
const box = await chartCanvas.boundingBox();
|
|
const position = box
|
|
? { x: Math.floor(box.width / 2), y: Math.floor(box.height / 2) }
|
|
: { x: 200, y: 100 };
|
|
return { chartCanvas, position };
|
|
}
|
|
|
|
// Verify alert context menu is visible
|
|
async expectAlertContextMenuVisible() {
|
|
await expect(this.alertContextMenu).toBeVisible({ timeout: 10000 });
|
|
}
|
|
|
|
// Verify alert context menu is hidden
|
|
async expectAlertContextMenuHidden() {
|
|
await expect(this.alertContextMenu).not.toBeVisible({ timeout: 5000 });
|
|
}
|
|
|
|
// Select "above threshold" from alert context menu
|
|
// dispatchEvent is used because AlertContextMenu uses <teleport to="body"> which
|
|
// places the menu under overlays that intercept pointer events from click({ force: true }).
|
|
// dispatchEvent('click') dispatches directly on the element through Playwright's locator path.
|
|
async selectAlertAboveThreshold() {
|
|
await this.alertContextMenuAbove.waitFor({ state: "visible" });
|
|
await this.alertContextMenuAbove.dispatchEvent("click");
|
|
}
|
|
|
|
// Select "below threshold" from alert context menu
|
|
// dispatchEvent is used because AlertContextMenu uses <teleport to="body"> which
|
|
// places the menu under overlays that intercept pointer events from click({ force: true }).
|
|
// dispatchEvent('click') dispatches directly on the element through Playwright's locator path.
|
|
async selectAlertBelowThreshold() {
|
|
await this.alertContextMenuBelow.waitFor({ state: "visible" });
|
|
await this.alertContextMenuBelow.dispatchEvent("click");
|
|
}
|
|
|
|
//open Query inspector
|
|
|
|
async openQueryInspector(panelName) {
|
|
const dropdownBtn = this.page.locator(`[data-test="dashboard-edit-panel-${panelName}-dropdown"]`);
|
|
|
|
// Wait for dropdown button to be visible with retry
|
|
await dropdownBtn.waitFor({ state: "visible", timeout: 15000 });
|
|
await dropdownBtn.click();
|
|
|
|
// Wait for query inspector option with retry
|
|
try {
|
|
await this.queryInspector.waitFor({ state: "visible", timeout: 10000 });
|
|
} catch (e) {
|
|
// Menu may not have opened, retry click — click outside to dismiss any open dropdown
|
|
await this.page.locator('body').click({ position: { x: 10, y: 10 } }).catch(() => {});
|
|
await this.queryInspector.waitFor({ state: "hidden", timeout: 5000 }).catch(() => {});
|
|
await dropdownBtn.click();
|
|
await this.queryInspector.waitFor({ state: "visible", timeout: 10000 });
|
|
}
|
|
await this.queryInspector.click();
|
|
}
|
|
|
|
// Return the panel-name element locator (edit panel header).
|
|
getPanelNameLocator() {
|
|
return this.namepanel;
|
|
}
|
|
|
|
// Return the dashboard panel container locator (hover target for panel menu).
|
|
getPanelContainer() {
|
|
return this.page.locator('[data-test="dashboard-panel-container"]');
|
|
}
|
|
|
|
// Create Alert menu item (dropdown item) for disabled/state assertions.
|
|
getCreateAlertFromPanelItem() {
|
|
return this.createAlertFromPanel;
|
|
}
|
|
|
|
// The disabled-reason description rendered under the item (ODropdownItem).
|
|
getCreateAlertFromPanelReason() {
|
|
return this.page.locator(
|
|
'[data-test="dashboard-create-alert-from-panel-reason"]'
|
|
);
|
|
}
|
|
|
|
// Alert context menu options (shown on chart right-click).
|
|
getAlertContextMenuAbove() {
|
|
return this.alertContextMenuAbove;
|
|
}
|
|
getAlertContextMenuBelow() {
|
|
return this.alertContextMenuBelow;
|
|
}
|
|
|
|
// Click on an empty page corner to dismiss the alert context menu.
|
|
async clickOutsideContextMenu() {
|
|
await this.page.locator("body").click({ position: { x: 10, y: 10 } });
|
|
}
|
|
|
|
// Return the inspector query-editor entry that contains the given SQL text.
|
|
getInspectorQueryByText(sql) {
|
|
return this.inspectorQueryEditor.filter({ hasText: sql }).first();
|
|
}
|
|
|
|
// Close the query inspector dialog.
|
|
async closeQueryInspector() {
|
|
await this.queryInspectorCloseBtn.click();
|
|
}
|
|
}
|