diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b2aa0f1969a..fb0144ca5e8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -414,11 +414,13 @@ /e2e-playwright/dashboard-cujs/ @grafana/dashboards-squad /e2e-playwright/dashboards-search-suite/ @grafana/dashboards-squad /e2e-playwright/dashboards/cujs/ @grafana/dashboards-squad +/e2e-playwright/dashboards/AdHocFilterTest.json @grafana/datapro /e2e-playwright/dashboards/DashboardLiveTest.json @grafana/dashboards-squad /e2e-playwright/dashboards/DataLinkWithoutSlugTest.json @grafana/dashboards-squad /e2e-playwright/dashboards/PanelSandboxDashboard.json @grafana/plugins-platform-frontend /e2e-playwright/dashboards/TestDashboard.json @grafana/dashboards-squad @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards/TestV2Dashboard.json @grafana/dashboards-squad +/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts @grafana/datapro /e2e-playwright/dashboards-suite/dashboard-browse-nested.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-browse.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/dashboards-suite/dashboard-export-image.spec.ts @grafana/sharing-squad @@ -455,6 +457,7 @@ /e2e-playwright/fixtures/exemplars-query-response.json @grafana/observability-traces-and-profiling /e2e-playwright/fixtures/long-trace-response.json @grafana/observability-traces-and-profiling /e2e-playwright/fixtures/tempo-response.json @grafana/oss-big-tent +/e2e-playwright/fixtures/prometheus-response.json @grafana/datapro /e2e-playwright/panels-suite/dashlist.spec.ts @grafana/grafana-search-navigate-organise /e2e-playwright/panels-suite/datagrid-data-change.spec.ts @grafana/dataviz-squad /e2e-playwright/panels-suite/datagrid-editing-features.spec.ts @grafana/dataviz-squad diff --git a/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts b/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts new file mode 100644 index 00000000000..181aadb36cd --- /dev/null +++ b/e2e-playwright/dashboards-suite/adhoc-filter-from-panel.spec.ts @@ -0,0 +1,106 @@ +import { Page, Locator } from '@playwright/test'; + +import { test, expect } from '@grafana/plugin-e2e'; + +import testDashboard from '../dashboards/AdHocFilterTest.json'; + +// Helper function to get a specific cell in a table +const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) => + loc + .getByRole('row') + .nth(rowIdx) + .getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell') + .nth(colIdx); + +test.describe( + 'Dashboard with Table powered by Prometheus data source', + { + tag: ['@dashboards'], + }, + () => { + let dashboardUID: string; + + test.beforeAll(async ({ request }) => { + // Import the test dashboard + const response = await request.post('/api/dashboards/import', { + data: { + dashboard: testDashboard, + folderUid: '', + overwrite: true, + inputs: [], + }, + }); + const responseBody = await response.json(); + dashboardUID = responseBody.uid; + }); + + test.afterAll(async ({ request }) => { + // Clean up the imported dashboard + if (dashboardUID) { + await request.delete(`/api/dashboards/uid/${dashboardUID}`); + } + }); + + test('Should add adhoc filter when clicking "Filter for value" button on table cell', async ({ + page, + gotoDashboardPage, + selectors, + }) => { + // Handle query and query_range API calls + await page.route(/\/api\/ds\/query/, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify(require('../fixtures/prometheus-response.json')), + }); + }); + + const dashboardPage = await gotoDashboardPage({ uid: dashboardUID }); + + const panel = dashboardPage.getByGrafanaSelector( + selectors.components.Panels.Panel.title('Table powered by Prometheus') + ); + await expect(panel).toBeVisible(); + + // Wait for the table to load completely + await expect(panel.locator('.rdg')).toBeVisible(); + + // Get the first data cell in the third column (row 1, column 2) + const labelValueCell = await getCell(panel, 1, 1); + await expect(labelValueCell).toBeVisible(); + + // Get the cell value before clicking the filter button + const labelValue = await labelValueCell.textContent(); + expect(labelValue).toBeTruthy(); + + // Hover over the first cell to trigger the appearance of filter actions + await labelValueCell.hover(); + + // Check if the "Filter for value" button appears on hover + const filterForValueButton = labelValueCell.getByRole('button', { name: 'Filter for value' }); + await expect(filterForValueButton).toBeVisible(); + + // Click on the "Filter for value" button + await filterForValueButton.click(); + + // Check if the adhoc filter appears in the dashboard submenu + const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem); + await expect(submenuItems.first()).toBeVisible(); + + // Look for submenu items that contain the filtered value + // The adhoc filter should appear as a filter chip or within the variable controls + const hasFilterValue = await submenuItems.filter({ hasText: labelValue! }).count(); + expect(hasFilterValue).toBeGreaterThan(0); + + // Check if the URL contains the var-PromAdHoc parameter with the filtered value + const currentUrl = page.url(); + expect(currentUrl).toContain('var-PromAdHoc'); + + // The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"] + const urlParams = new URLSearchParams(new URL(currentUrl).search); + const promAdHocParam = urlParams.get('var-PromAdHoc'); + expect(promAdHocParam).toBeTruthy(); + expect(promAdHocParam).toContain(labelValue!); + }); + } +); diff --git a/e2e-playwright/dashboards/AdHocFilterTest.json b/e2e-playwright/dashboards/AdHocFilterTest.json new file mode 100644 index 00000000000..28db6228d67 --- /dev/null +++ b/e2e-playwright/dashboards/AdHocFilterTest.json @@ -0,0 +1,126 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "12.2.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "topk(2, sum by(slice) (prometheus_engine_query_duration_seconds))", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "Table powered by Prometheus", + "type": "table" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [ + { + "baseFilters": [], + "datasource": { + "type": "prometheus", + "uid": "gdev-prometheus" + }, + "description": "AdHoc filters acting on the Prometheus data source", + "filters": [], + "label": "Prometheus filters", + "name": "PromAdHoc", + "type": "adhoc" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "AdHoc filter test", + "uid": "adhoch-filter-test", + "version": 3 +} diff --git a/e2e-playwright/fixtures/prometheus-response.json b/e2e-playwright/fixtures/prometheus-response.json new file mode 100644 index 00000000000..85ad57a50d8 --- /dev/null +++ b/e2e-playwright/fixtures/prometheus-response.json @@ -0,0 +1,85 @@ +{ + "results": { + "A": { + "status": 200, + "frames": [ + { + "schema": { + "refId": "A", + "meta": { + "type": "numeric-multi", + "typeVersion": [0, 1], + "custom": { + "calculatedMinStep": 30000, + "resultType": "vector" + }, + "executedQueryString": "Expr: topk(2, sum by(slice) (prometheus_engine_query_duration_seconds))\nStep: 30s" + }, + "fields": [ + { + "name": "Time", + "type": "time", + "typeInfo": { + "frame": "time.Time" + }, + "config": { + "interval": 30000 + } + }, + { + "name": "Value", + "type": "number", + "typeInfo": { + "frame": "float64" + }, + "labels": { + "slice": "inner_eval" + } + } + ] + }, + "data": { + "values": [[1756995119802], [0.001445292]] + } + }, + { + "schema": { + "refId": "A", + "meta": { + "type": "numeric-multi", + "typeVersion": [0, 1], + "custom": { + "resultType": "vector" + } + }, + "fields": [ + { + "name": "Time", + "type": "time", + "typeInfo": { + "frame": "time.Time" + }, + "config": { + "interval": 30000 + } + }, + { + "name": "Value", + "type": "number", + "typeInfo": { + "frame": "float64" + }, + "labels": { + "slice": "prepare_time" + } + } + ] + }, + "data": { + "values": [[1756995119802], [0.001351875]] + } + } + ] + } + } +}