From b20017cf8cac83ab523f09b71f62f64fc0e66ce7 Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:47:15 +0100 Subject: [PATCH] Restore Dashboards: Add e2e tests (#92514) --- e2e/dashboards-suite/dashboard-browse.spec.ts | 8 + .../dashboard-restore.spec.ts | 82 +++++++ e2e/dashboards/TestRestoreDashboard.json | 209 ++++++++++++++++++ e2e/utils/flows/deleteDashboard.ts | 1 + .../src/selectors/pages.ts | 8 + .../dashboard/components/DashNav/DashNav.tsx | 1 + .../page/components/SearchResultsTable.tsx | 17 +- scripts/grafana-server/custom.ini | 2 +- 8 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 e2e/dashboards-suite/dashboard-restore.spec.ts create mode 100644 e2e/dashboards/TestRestoreDashboard.json diff --git a/e2e/dashboards-suite/dashboard-browse.spec.ts b/e2e/dashboards-suite/dashboard-browse.spec.ts index 4e9728b7a65..a39c0ca373d 100644 --- a/e2e/dashboards-suite/dashboard-browse.spec.ts +++ b/e2e/dashboards-suite/dashboard-browse.spec.ts @@ -49,4 +49,12 @@ describe('Dashboard browse', () => { e2e.flows.confirmDelete(); e2e.pages.BrowseDashboards.table.row('E2E Test - Import Dashboard').should('not.exist'); }); + + afterEach(() => { + // Permanently delete dashboard + e2e.pages.RecentlyDeleted.visit(); + e2e.pages.Search.table.row('E2E Test - Import Dashboard').find('[type="checkbox"]').click({ force: true }); + cy.contains('button', 'Delete permanently').click(); + e2e.flows.confirmDelete(); + }); }); diff --git a/e2e/dashboards-suite/dashboard-restore.spec.ts b/e2e/dashboards-suite/dashboard-restore.spec.ts new file mode 100644 index 00000000000..270b9794e31 --- /dev/null +++ b/e2e/dashboards-suite/dashboard-restore.spec.ts @@ -0,0 +1,82 @@ +import testDashboard from '../dashboards/TestRestoreDashboard.json'; +import { e2e } from '../utils'; + +describe('Dashboard restore', () => { + beforeEach(() => { + e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); + }); + + it('Should delete, restore and permanently delete from the Dashboards page', () => { + e2e.flows.importDashboard(testDashboard, 1000, true); + + e2e.pages.Dashboards.visit(); + + // Delete dashboard + e2e.pages.BrowseDashboards.table + .row('E2E Test - Restore Dashboard') + .find('[type="checkbox"]') + .click({ force: true }); + deleteDashboard('Delete'); + + // Dashboard should appear in Recently Deleted + e2e.pages.RecentlyDeleted.visit(); + e2e.pages.Search.table.row('E2E Test - Restore Dashboard').should('exist'); + + // Restore dashboard + e2e.pages.Search.table.row('E2E Test - Restore Dashboard').find('[type="checkbox"]').click({ force: true }); + cy.contains('button', 'Restore').click(); + cy.contains('p', 'This action will restore 1 dashboard.').should('be.visible'); + e2e.pages.ConfirmModal.delete().click(); + e2e.components.Alert.alertV2('success').contains('Dashboard E2E Test - Restore Dashboard restored').should('exist'); + + // Dashboard should appear in Browse + e2e.pages.Dashboards.visit(); + e2e.pages.BrowseDashboards.table.row('E2E Test - Restore Dashboard').should('exist'); + + // Delete dashboard + e2e.pages.BrowseDashboards.table + .row('E2E Test - Restore Dashboard') + .find('[type="checkbox"]') + .click({ force: true }); + deleteDashboard('Delete'); + + // Permanently delete dashboard + permanentlyDeleteDashboard(); + }); + + it('Should delete, restore and permanently delete from the Dashboard settings', () => { + e2e.flows.importDashboard(testDashboard, 1000, true); + + e2e.flows.openDashboard({ uid: '355ac6c2-8a12-4469-8b99-4750eb8d0966' }); + e2e.pages.Dashboard.DashNav.settingsButton().click(); + deleteDashboard('Delete dashboard'); + + // Permanently delete dashboard + permanentlyDeleteDashboard(); + }); +}); + +const deleteDashboard = (buttonName: string) => { + cy.contains('button', buttonName).click(); + e2e.flows.confirmDelete(); + e2e.components.Alert.alertV2('success') + .contains('Dashboard E2E Test - Restore Dashboard moved to Recently deleted') + .should('exist'); + e2e.pages.BrowseDashboards.table.row('E2E Test - Restore Dashboard').should('not.exist'); +}; + +const permanentlyDeleteDashboard = () => { + // Permanently delete dashboard + e2e.pages.RecentlyDeleted.visit(); + e2e.pages.Search.table.row('E2E Test - Restore Dashboard').find('[type="checkbox"]').click({ force: true }); + cy.contains('button', 'Delete permanently').click(); + cy.contains('p', 'This action will delete 1 dashboard.').should('be.visible'); + e2e.flows.confirmDelete(); + e2e.components.Alert.alertV2('success').contains('Dashboard E2E Test - Restore Dashboard deleted').should('exist'); + + // Dashboard should not appear in Recently Deleted or Browse + e2e.pages.Search.table.row('E2E Test - Restore Dashboard').should('not.exist'); + + e2e.pages.Dashboards.visit(); + e2e.pages.BrowseDashboards.table.row('E2E Test - Restore Dashboard').should('not.exist'); +}; diff --git a/e2e/dashboards/TestRestoreDashboard.json b/e2e/dashboards/TestRestoreDashboard.json new file mode 100644 index 00000000000..b34ff4c0c05 --- /dev/null +++ b/e2e/dashboards/TestRestoreDashboard.json @@ -0,0 +1,209 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 322, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 6, + "options": { + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "8.3.0-pre", + "title": "Gauge Example", + "type": "gauge" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.3.0-pre", + "title": "Stat", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single" + } + }, + "title": "Time series example", + "type": "timeseries" + } + ], + "refresh": false, + "schemaVersion": 31, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "2021-09-01T04:00:00.000Z", + "to": "2021-09-15T04:00:00.000Z" + }, + "timepicker": {}, + "timezone": "", + "title": "E2E Test - Restore Dashboard", + "uid": "355ac6c2-8a12-4469-8b99-4750eb8d0966", + "version": 4 +} diff --git a/e2e/utils/flows/deleteDashboard.ts b/e2e/utils/flows/deleteDashboard.ts index de492012c2f..6b39a0020a8 100644 --- a/e2e/utils/flows/deleteDashboard.ts +++ b/e2e/utils/flows/deleteDashboard.ts @@ -29,6 +29,7 @@ export const deleteDashboard = ({ quick = false, title, uid }: DeleteDashboardCo const quickDelete = (uid: string) => { cy.request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}`)); + cy.request('DELETE', fromBaseUrl(`/api/dashboards/uid/${uid}/trash`)); }; const uiDelete = (uid: string, title: string) => { diff --git a/packages/grafana-e2e-selectors/src/selectors/pages.ts b/packages/grafana-e2e-selectors/src/selectors/pages.ts index 45c453130fa..b0a8b9710cf 100644 --- a/packages/grafana-e2e-selectors/src/selectors/pages.ts +++ b/packages/grafana-e2e-selectors/src/selectors/pages.ts @@ -57,6 +57,7 @@ export const Pages = { navV2: 'data-testid Dashboard navigation', publicDashboardTag: 'data-testid public dashboard tag', shareButton: 'data-testid share-button', + settingsButton: 'data-testid settings-button', scrollContainer: 'data-testid Dashboard canvas scroll container', newShareButton: { container: 'data-testid new share button', @@ -239,6 +240,9 @@ export const Pages = { */ dashboards: (title: string) => `Dashboard search item ${title}`, }, + RecentlyDeleted: { + url: '/dashboard/recently-deleted', + }, SaveDashboardAsModal: { newName: 'Save dashboard title field', save: 'Save dashboard button', @@ -375,6 +379,10 @@ export const Pages = { FolderView: { url: '/?search=open&layout=folders', }, + table: { + body: 'data-testid search-table', + row: (name: string) => `data-testid search row ${name}`, + }, }, PublicDashboards: { ListItem: { diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 86a3986be31..b0bb9796b16 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -326,6 +326,7 @@ export const DashNav = memo((props) => { diff --git a/public/app/features/search/page/components/SearchResultsTable.tsx b/public/app/features/search/page/components/SearchResultsTable.tsx index 20ae3c292ff..471bdeb0108 100644 --- a/public/app/features/search/page/components/SearchResultsTable.tsx +++ b/public/app/features/search/page/components/SearchResultsTable.tsx @@ -7,6 +7,7 @@ import InfiniteLoader from 'react-window-infinite-loader'; import { Observable } from 'rxjs'; import { Field, GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { TableCellHeight } from '@grafana/schema'; import { useStyles2, useTheme2 } from '@grafana/ui'; import { TableCell } from '@grafana/ui/src/components/Table/TableCell'; @@ -137,9 +138,13 @@ export const SearchResultsTable = React.memo( className += ' ' + styles.selectedRow; } const { key, ...rowProps } = row.getRowProps({ style }); - return ( -
+
{row.cells.map((cell: Cell, index: number) => { return ( +
{headerGroups.map((headerGroup) => { const { key, ...headerGroupProps } = headerGroup.getHeaderGroupProps({ style: { width }, diff --git a/scripts/grafana-server/custom.ini b/scripts/grafana-server/custom.ini index 5d09d40ac63..d1751079a18 100644 --- a/scripts/grafana-server/custom.ini +++ b/scripts/grafana-server/custom.ini @@ -4,7 +4,7 @@ content_security_policy = true content_security_policy_template = """require-trusted-types-for 'script'; script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" [feature_toggles] -enable = publicDashboards +enable = publicDashboards, dashboardRestore, dashboardRestoreUI [plugins] allow_loading_unsigned_plugins=grafana-extensionstest-app,grafana-extensionexample1-app,grafana-extensionexample2-app,