From eba4c8ecc2f13abcc32affd8760aee965b59bf4b Mon Sep 17 00:00:00 2001 From: Clarity-89 Date: Mon, 5 Jan 2026 13:46:43 +0200 Subject: [PATCH] Restore dashboards: Update permissions --- .../src/internal/openFeature/index.ts | 1 + pkg/services/navtree/navtreeimpl/navtree.go | 2 +- .../BrowseDashboardsPage.test.tsx | 23 +++++++++++++++++++ .../BrowseDashboardsPage.tsx | 4 +--- .../components/BrowseActions/DeleteModal.tsx | 6 ++--- .../components/RecentlyDeletedEmptyState.tsx | 4 ++-- .../settings/DeleteDashboardButton.tsx | 5 ++-- public/app/routes/routes.tsx | 2 +- 8 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/grafana-runtime/src/internal/openFeature/index.ts b/packages/grafana-runtime/src/internal/openFeature/index.ts index 57e35516e30..4e24db0d676 100644 --- a/packages/grafana-runtime/src/internal/openFeature/index.ts +++ b/packages/grafana-runtime/src/internal/openFeature/index.ts @@ -29,5 +29,6 @@ export async function initOpenFeature() { } export function evaluateBooleanFlag(flagName: FeatureFlagName, defaultValue: boolean): boolean { + console.log('deets', OpenFeature.getClient().getBooleanDetails('recentlyViewedDashboards', false)); return OpenFeature.getClient().getBooleanValue(flagName, defaultValue); } diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 5b9eb985bd8..40382840dd5 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -408,7 +408,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt } //nolint:staticcheck // not yet migrated to OpenFeature - if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagRestoreDashboards) && (c.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagRestoreDashboards) && hasAccess(ac.EvalPermission(dashboards.ActionDashboardsDelete)) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Recently deleted", SubTitle: "Any items listed here for more than 30 days will be automatically deleted.", diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx index 67888b34e14..ce1ea7d4a31 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx @@ -113,6 +113,29 @@ describe('browse-dashboards BrowseDashboardsPage', () => { expect(await screen.findByRole('button', { name: 'New' })).toBeInTheDocument(); }); + it('shows the "Recently deleted" button when restore is enabled and user can delete dashboards', async () => { + const previousFlag = config.featureToggles.restoreDashboards; + config.featureToggles.restoreDashboards = true; + + render(); + await screen.findByPlaceholderText('Search for dashboards and folders'); + expect(await screen.findByRole('link', { name: 'Recently deleted' })).toBeInTheDocument(); + + config.featureToggles.restoreDashboards = previousFlag; + }); + + it('does not show the "Recently deleted" button when user cannot delete dashboards', async () => { + const previousFlag = config.featureToggles.restoreDashboards; + config.featureToggles.restoreDashboards = true; + mockPermissions.canDeleteDashboards = false; + + render(); + await screen.findByPlaceholderText('Search for dashboards and folders'); + expect(screen.queryByRole('link', { name: 'Recently deleted' })).not.toBeInTheDocument(); + + config.featureToggles.restoreDashboards = previousFlag; + }); + it('does not show the "New" button if the user does not have permissions', async () => { mockPermissions.canCreateDashboards = false; mockPermissions.canCreateFolders = false; diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index e177fcf9fa2..08fca5cb92f 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -13,7 +13,6 @@ import { getConfig } from 'app/core/config'; import { useDispatch } from 'app/types/store'; import { FolderRepo } from '../../core/components/NestedFolderPicker/FolderRepo'; -import { contextSrv } from '../../core/services/context_srv'; import { ManagerKind } from '../apiserver/types'; import { TemplateDashboardModal } from '../dashboard/dashgrid/DashboardLibrary/TemplateDashboardModal'; import { buildNavModel, getDashboardsTabID } from '../folders/state/navModel'; @@ -104,7 +103,6 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record - {config.featureToggles.restoreDashboards && hasAdminRights && ( + {config.featureToggles.restoreDashboards && canDeleteDashboards && ( This action will delete the selected folders immediately. Deleted dashboards will be kept in the - history for up to 12 months and can be restored by your organization administrator during that time. - The history is limited to 1000 dashboards — older ones may be removed sooner if the limit is reached. - Folders cannot be restored. + history for up to 12 months. Users with delete permissions can restore the dashboards they deleted, + and admins can restore any user's deleted dashboards. The history is limited to 1000 dashboards — older + ones may be removed sooner if the limit is reached. Folders cannot be restored. diff --git a/public/app/features/browse-dashboards/components/RecentlyDeletedEmptyState.tsx b/public/app/features/browse-dashboards/components/RecentlyDeletedEmptyState.tsx index 416d51236fc..cee0c3ed3ce 100644 --- a/public/app/features/browse-dashboards/components/RecentlyDeletedEmptyState.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyDeletedEmptyState.tsx @@ -19,8 +19,8 @@ export const RecentlyDeletedEmptyState = ({ searchState }: RecentlyDeletedEmptyS role="alert" > - When you delete a dashboard, it will appear here for 30 days before being permanently deleted. Your organization - administrator can restore recently-deleted dashboards. + When you delete a dashboard, it will appear here for 30 days before being permanently deleted. Users with delete + permissions can restore the dashboards they deleted, and admins can restore any user's deleted dashboards. ); diff --git a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx index 033408be955..dac246b0a62 100644 --- a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx @@ -81,8 +81,9 @@ export function DeleteDashboardModal({ dashboardTitle, onConfirm, onClose }: Del This action will delete the dashboard. Deleted dashboards will be kept in the history for up to 12 - months and can be restored by your organization administrator during that time. The history is limited - to 1000 dashboards—older ones will be removed sooner if the limit is reached. + months. Users with delete permissions can restore the dashboards they deleted, and admins can restore + any user's deleted dashboards. The history is limited to 1000 dashboards—older ones will be removed + sooner if the limit is reached. diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 78cf632a1b1..d1e5510ea9f 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -534,7 +534,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, config.featureToggles.restoreDashboards && { path: '/dashboard/recently-deleted', - roles: () => ['Admin', 'ServerAdmin'], + roles: () => contextSrv.evaluatePermission([AccessControlAction.DashboardsDelete]), component: SafeDynamicImport( () => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/browse-dashboards/RecentlyDeletedPage') ),