Restore dashboards: Re-enable FE feature toggle (#105820)

* Add dashboardRestore toggle

* Restore the toggle on FE

* Add navtree item

* Fix lint

* Rename feature toggle

* Use the renamed toggle
This commit is contained in:
Alex Khomenko
2025-05-23 17:35:54 +03:00
committed by GitHub
parent 8734b54f90
commit 0cb6f9584b
14 changed files with 85 additions and 23 deletions
@@ -1028,4 +1028,9 @@ export interface FeatureToggles {
* Use proxy-based read-only objects for plugin extensions instead of deep cloning
*/
extensionsReadOnlyProxy?: boolean;
/**
* Enables restore deleted dashboards feature
* @default false
*/
restoreDashboards?: boolean;
}
+8
View File
@@ -1767,6 +1767,14 @@ var (
HideFromDocs: true,
FrontendOnly: true,
},
{
Name: "restoreDashboards",
Description: "Enables restore deleted dashboards feature",
Stage: FeatureStageExperimental,
Owner: grafanaFrontendPlatformSquad,
HideFromAdminPage: true,
Expression: "false",
},
}
)
+1
View File
@@ -231,3 +231,4 @@ alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,fal
alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false
alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true
extensionsReadOnlyProxy,experimental,@grafana/plugins-platform-backend,false,false,true
restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
231 alertRuleUseFiredAtForStartsAt experimental @grafana/alerting-squad false false false
232 alertingBulkActionsInUI GA @grafana/alerting-squad false false true
233 extensionsReadOnlyProxy experimental @grafana/plugins-platform-backend false false true
234 restoreDashboards experimental @grafana/grafana-frontend-platform false false false
+4
View File
@@ -934,4 +934,8 @@ const (
// FlagExtensionsReadOnlyProxy
// Use proxy-based read-only objects for plugin extensions instead of deep cloning
FlagExtensionsReadOnlyProxy = "extensionsReadOnlyProxy"
// FlagRestoreDashboards
// Enables restore deleted dashboards feature
FlagRestoreDashboards = "restoreDashboards"
)
+29
View File
@@ -777,6 +777,21 @@
"frontend": true
}
},
{
"metadata": {
"name": "dashboardRestore",
"resourceVersion": "1747744953711",
"creationTimestamp": "2024-05-16T17:36:26Z",
"deletionTimestamp": "2025-04-03T07:52:54Z"
},
"spec": {
"description": "Enables deleted dashboard restore feature",
"stage": "experimental",
"codeowner": "@grafana/grafana-frontend-platform",
"hideFromAdminPage": true,
"expression": "false"
}
},
{
"metadata": {
"name": "dashboardScene",
@@ -2897,6 +2912,20 @@
"expression": "true"
}
},
{
"metadata": {
"name": "restoreDashboards",
"resourceVersion": "1748002635285",
"creationTimestamp": "2025-05-23T12:17:15Z"
},
"spec": {
"description": "Enables restore deleted dashboards feature",
"stage": "experimental",
"codeowner": "@grafana/grafana-frontend-platform",
"hideFromAdminPage": true,
"expression": "false"
}
},
{
"metadata": {
"name": "rolePickerDrawer",
@@ -399,6 +399,15 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt
Icon: "library-panel",
})
}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagRestoreDashboards) && (c.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) {
dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{
Text: "Recently deleted",
SubTitle: "Any items listed here for more than 30 days will be automatically deleted.",
Id: "dashboards/recently-deleted",
Url: s.cfg.AppSubURL + "/dashboard/recently-deleted",
})
}
}
if hasAccess(ac.EvalPermission(dashboards.ActionDashboardsCreate)) {
@@ -6,7 +6,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { config, reportInteraction } from '@grafana/runtime';
import { LinkButton, FilterInput, useStyles2, Text, Stack } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { getConfig } from 'app/core/config';
@@ -137,16 +137,15 @@ const BrowseDashboardsPage = memo(() => {
renderTitle={renderTitle}
actions={
<>
{false &&
hasAdminRights && ( // TODO: change this to a feature flag when dashboard restore is reworked
<LinkButton
variant="secondary"
href={getConfig().appSubUrl + '/dashboard/recently-deleted'}
onClick={handleButtonClickToRecentlyDeleted}
>
<Trans i18nKey="browse-dashboards.actions.button-to-recently-deleted">Recently deleted</Trans>
</LinkButton>
)}
{config.featureToggles.restoreDashboards && hasAdminRights && (
<LinkButton
variant="secondary"
href={getConfig().appSubUrl + '/dashboard/recently-deleted'}
onClick={handleButtonClickToRecentlyDeleted}
>
<Trans i18nKey="browse-dashboards.actions.button-to-recently-deleted">Recently deleted</Trans>
</LinkButton>
)}
{folderDTO && <FolderActionsButton folder={folderDTO} />}
{(canCreateDashboards || canCreateFolders) && (
<CreateNewButton
@@ -116,6 +116,6 @@ function trackAction(action: keyof typeof actionMap, selectedItems: Omit<Dashboa
dashboard: selectedDashboards.length,
},
source: 'tree_actions',
restore_enabled: false,
restore_enabled: Boolean(config.featureToggles.restoreDashboards),
});
}
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Trans, useTranslate } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { config, reportInteraction } from '@grafana/runtime';
import { Alert, ConfirmModal, Text, Space } from '@grafana/ui';
import { useGetAffectedItemsQuery } from '../../api/browseDashboardsAPI';
@@ -44,7 +44,7 @@ export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: P
<ConfirmModal
body={
<>
{false && ( // TODO: change this to a feature flag when dashboard restore is reworked
{config.featureToggles.restoreDashboards && (
<>
<Text element="p">
<Trans i18nKey="browse-dashboards.action.delete-modal-restore-dashboards-text">
@@ -2,7 +2,7 @@ import * as React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, useTranslate } from '@grafana/i18n';
import { isFetchError } from '@grafana/runtime';
import { config, isFetchError } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2alpha1/types.spec.gen';
import { Alert, Box, Button, Stack } from '@grafana/ui';
@@ -42,7 +42,7 @@ export interface NameAlreadyExistsErrorProps {
export function NameAlreadyExistsError({ cancelButton, saveButton }: NameAlreadyExistsErrorProps) {
const { t } = useTranslate();
const isRestoreDashboardsEnabled = false;
const isRestoreDashboardsEnabled = config.featureToggles.restoreDashboards;
return isRestoreDashboardsEnabled ? (
<Alert title={t('save-dashboards.name-exists.title', 'Dashboard name already exists')} severity="error">
<p>
@@ -2,7 +2,7 @@ import { useAsyncFn, useToggle } from 'react-use';
import { selectors } from '@grafana/e2e-selectors';
import { Trans, useTranslate } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { config, reportInteraction } from '@grafana/runtime';
import { Button, ConfirmModal, Modal, Space, Text, TextLink } from '@grafana/ui';
import { useDeleteItemsMutation } from '../../browse-dashboards/api/browseDashboardsAPI';
@@ -34,7 +34,7 @@ export function DeleteDashboardButton({ dashboard }: ButtonProps) {
dashboard: 1,
},
source: 'dashboard_scene_settings',
restore_enabled: false,
restore_enabled: Boolean(config.featureToggles.restoreDashboards),
});
toggleModal();
if (dashboard.state.uid) {
@@ -83,7 +83,7 @@ export function DeleteDashboardModal({ dashboardTitle, onConfirm, onClose }: Del
isOpen={true}
body={
<>
{false && ( // TODO: re-enable when restore is reworked
{config.featureToggles.restoreDashboards && (
<>
<Text element="p">
<Trans i18nKey="dashboard-settings.delete-modal-restore-dashboards-text">
@@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { Trans, useTranslate } from '@grafana/i18n';
import { locationService, reportInteraction } from '@grafana/runtime';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { Modal, Button, Text, Space, TextLink } from '@grafana/ui';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { cleanUpDashboardAndVariables } from 'app/features/dashboard/state/actions';
@@ -34,7 +34,7 @@ const DeleteDashboardModalUnconnected = ({ hideModal, cleanUpDashboardAndVariabl
dashboard: 1,
},
source: 'dashboard_settings',
restore_enabled: false,
restore_enabled: Boolean(config.featureToggles.restoreDashboards),
});
await deleteItems({
selectedItems: {
@@ -3,7 +3,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans, useTranslate } from '@grafana/i18n';
import { FetchError } from '@grafana/runtime';
import { config, FetchError } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { Button, ConfirmModal, Modal, useStyles2 } from '@grafana/ui';
@@ -32,7 +32,7 @@ export const SaveDashboardErrorProxy = ({
}: SaveDashboardErrorProxyProps) => {
const { onDashboardSave } = useDashboardSave();
const { t } = useTranslate();
const isRestoreDashboardsEnabled = false;
const isRestoreDashboardsEnabled = config.featureToggles.restoreDashboards;
return (
<>
+7
View File
@@ -527,6 +527,13 @@ export function getAppRoutes(): RouteDescriptor[] {
() => import(/* webpackChunkName: "BookmarksPage"*/ 'app/features/bookmarks/BookmarksPage')
),
},
config.featureToggles.restoreDashboards && {
path: '/dashboard/recently-deleted',
roles: () => ['Admin', 'ServerAdmin'],
component: SafeDynamicImport(
() => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/browse-dashboards/RecentlyDeletedPage')
),
},
{
// Redirect the /femt dev page to the root
path: '/femt',