diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
index eec5f5be414..56a8ee3bf47 100644
--- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
+++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
@@ -28,7 +28,7 @@ Some features are enabled by default. You can disable these feature by setting t
| `redshiftAsyncQueryDataSupport` | Enable async query data support for Redshift | Yes |
| `athenaAsyncQueryDataSupport` | Enable async query data support for Athena | Yes |
| `newPanelChromeUI` | Show updated look and feel of grafana-ui PanelChrome: panel header, icons, and menu | Yes |
-| `nestedFolderPicker` | Enables the new folder picker to work with nested folders. Requires the folderPicker feature flag | Yes |
+| `nestedFolderPicker` | Enables the new folder picker to work with nested folders. Requires the nestedFolders feature flag | Yes |
| `accessTokenExpirationCheck` | Enable OAuth access_token expiration check and token refresh using the refresh_token | |
| `emptyDashboardPage` | Enable the redesigned user interface of a dashboard page that includes no panels | Yes |
| `disablePrometheusExemplarSampling` | Disable Prometheus exemplar sampling | |
@@ -72,6 +72,7 @@ Some features are enabled by default. You can disable these feature by setting t
| `sqlDatasourceDatabaseSelection` | Enables previous SQL data source dataset dropdown behavior |
| `splitScopes` | Support faster dashboard and folder search by splitting permission scopes into parts |
| `reportingRetries` | Enables rendering retries for the reporting feature |
+| `newBrowseDashboards` | New browse/manage dashboards UI |
## Experimental feature toggles
diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts
index b7c0acc3ec1..8c46d2e8dea 100644
--- a/packages/grafana-data/src/types/featureToggles.gen.ts
+++ b/packages/grafana-data/src/types/featureToggles.gen.ts
@@ -122,4 +122,5 @@ export interface FeatureToggles {
angularDeprecationUI?: boolean;
dashgpt?: boolean;
reportingRetries?: boolean;
+ newBrowseDashboards?: boolean;
}
diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go
index 380e255419d..03b139d333e 100644
--- a/pkg/services/featuremgmt/registry.go
+++ b/pkg/services/featuremgmt/registry.go
@@ -230,7 +230,7 @@ var (
},
{
Name: "nestedFolderPicker",
- Description: "Enables the new folder picker to work with nested folders. Requires the folderPicker feature flag",
+ Description: "Enables the new folder picker to work with nested folders. Requires the nestedFolders feature flag",
Stage: FeatureStageGeneralAvailability,
Owner: grafanaFrontendPlatformSquad,
FrontendOnly: true,
@@ -724,5 +724,12 @@ var (
Owner: grafanaSharingSquad,
RequiresRestart: true,
},
+ {
+ Name: "newBrowseDashboards",
+ Description: "New browse/manage dashboards UI",
+ Stage: FeatureStagePublicPreview,
+ Owner: grafanaFrontendPlatformSquad,
+ FrontendOnly: true,
+ },
}
)
diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv
index efda0bae398..278a58b2dae 100644
--- a/pkg/services/featuremgmt/toggles_gen.csv
+++ b/pkg/services/featuremgmt/toggles_gen.csv
@@ -103,3 +103,4 @@ alertingNoDataErrorExecution,privatePreview,@grafana/alerting-squad,false,false,
angularDeprecationUI,experimental,@grafana/plugins-platform-backend,false,false,false,true
dashgpt,experimental,@grafana/dashboards-squad,false,false,false,true
reportingRetries,preview,@grafana/sharing-squad,false,false,true,false
+newBrowseDashboards,preview,@grafana/grafana-frontend-platform,false,false,false,true
diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go
index 4acd886b2f1..135d1eaf670 100644
--- a/pkg/services/featuremgmt/toggles_gen.go
+++ b/pkg/services/featuremgmt/toggles_gen.go
@@ -140,7 +140,7 @@ const (
FlagNestedFolders = "nestedFolders"
// FlagNestedFolderPicker
- // Enables the new folder picker to work with nested folders. Requires the folderPicker feature flag
+ // Enables the new folder picker to work with nested folders. Requires the nestedFolders feature flag
FlagNestedFolderPicker = "nestedFolderPicker"
// FlagAccessTokenExpirationCheck
@@ -422,4 +422,8 @@ const (
// FlagReportingRetries
// Enables rendering retries for the reporting feature
FlagReportingRetries = "reportingRetries"
+
+ // FlagNewBrowseDashboards
+ // New browse/manage dashboards UI
+ FlagNewBrowseDashboards = "newBrowseDashboards"
)
diff --git a/public/app/core/components/AccessControl/Permissions.tsx b/public/app/core/components/AccessControl/Permissions.tsx
index 599b6bfa9bf..8e41b7540db 100644
--- a/public/app/core/components/AccessControl/Permissions.tsx
+++ b/public/app/core/components/AccessControl/Permissions.tsx
@@ -4,12 +4,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Space } from '@grafana/experimental';
-import { config } from '@grafana/runtime';
import { Button, useStyles2 } from '@grafana/ui';
import { SlideDown } from 'app/core/components/Animations/SlideDown';
import { Trans, t } from 'app/core/internationalization';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { DescendantCount } from 'app/features/browse-dashboards/components/BrowseActions/DescendantCount';
+import { newBrowseDashboardsEnabled } from 'app/features/browse-dashboards/featureFlag';
import { AddPermission } from './AddPermission';
import { PermissionList } from './PermissionList';
@@ -140,7 +140,7 @@ export const Permissions = ({
{canSetPermissions && (
<>
- {config.featureToggles.nestedFolders && resource === 'folders' && (
+ {newBrowseDashboardsEnabled() && resource === 'folders' && (
<>
This will change permissions for this folder and all its descendants. In total, this will affect:
diff --git a/public/app/core/components/Select/OldFolderPicker.tsx b/public/app/core/components/Select/OldFolderPicker.tsx
index 9f2dc87bb1d..77fbf504c08 100644
--- a/public/app/core/components/Select/OldFolderPicker.tsx
+++ b/public/app/core/components/Select/OldFolderPicker.tsx
@@ -5,11 +5,12 @@ import { useAsync } from 'react-use';
import { AppEvents, SelectableValue, GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
-import { config, reportInteraction } from '@grafana/runtime';
+import { reportInteraction } from '@grafana/runtime';
import { useStyles2, ActionMeta, Input, InputActionMeta, AsyncVirtualizedSelect } from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
+import { newBrowseDashboardsEnabled } from 'app/features/browse-dashboards/featureFlag';
import { createFolder, getFolderByUid, searchFolders } from 'app/features/manage-dashboards/state/actions';
import { DashboardSearchHit } from 'app/features/search/types';
import { AccessControlAction, PermissionLevelString, SearchQueryType } from 'app/types';
@@ -80,7 +81,7 @@ export function OldFolderPicker(props: Props) {
folderWarning,
} = props;
- const rootName = rootNameProp ?? config.featureToggles.nestedFolders ? 'Dashboards' : 'General';
+ const rootName = rootNameProp ?? newBrowseDashboardsEnabled() ? 'Dashboards' : 'General';
const [folder, setFolder] = useState(null);
const [isCreatingNew, setIsCreatingNew] = useState(false);
diff --git a/public/app/core/selectors/navModel.ts b/public/app/core/selectors/navModel.ts
index 4a966ff6250..c7c15a4fc74 100644
--- a/public/app/core/selectors/navModel.ts
+++ b/public/app/core/selectors/navModel.ts
@@ -1,5 +1,5 @@
import { NavModel, NavModelItem, NavIndex } from '@grafana/data';
-import { config } from '@grafana/runtime';
+import { newBrowseDashboardsEnabled } from 'app/features/browse-dashboards/featureFlag';
import { FOLDER_ID } from 'app/features/folders/state/navModel';
import { HOME_NAV_ID } from '../reducers/navModel';
@@ -40,7 +40,7 @@ export const getNavModel = (navIndex: NavIndex, id: string, fallback?: NavModel,
export function getRootSectionForNode(node: NavModelItem): NavModelItem {
// Don't recurse fully up the folder tree when nested folders is enabled
- if (config.featureToggles.nestedFolders && node.id === FOLDER_ID) {
+ if (newBrowseDashboardsEnabled() && node.id === FOLDER_ID) {
return node;
} else {
return node.parentItem && node.parentItem.id !== HOME_NAV_ID ? getRootSectionForNode(node.parentItem) : node;
diff --git a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts
index c2b8d63189f..fc68ca87565 100644
--- a/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts
+++ b/public/app/features/browse-dashboards/api/browseDashboardsAPI.ts
@@ -173,12 +173,11 @@ export const browseDashboardsAPI = createApi({
};
for (const folderCounts of results) {
- totalCounts.folder += folderCounts.folder;
+ // TODO remove nullish coalescing once nestedFolders is toggled on
+ totalCounts.folder += folderCounts.folder ?? 0;
totalCounts.dashboard += folderCounts.dashboard;
- totalCounts.alertRule += folderCounts.alertrule ?? 0;
-
- // TODO enable these once the backend correctly returns them
- // totalCounts.libraryPanel += folderCounts.libraryPanel;
+ totalCounts.alertRule += folderCounts.alertrule;
+ totalCounts.libraryPanel += folderCounts.librarypanel;
}
return { data: totalCounts };
diff --git a/public/app/features/browse-dashboards/api/services.ts b/public/app/features/browse-dashboards/api/services.ts
index 360fee5ab41..d8d43c5d222 100644
--- a/public/app/features/browse-dashboards/api/services.ts
+++ b/public/app/features/browse-dashboards/api/services.ts
@@ -1,4 +1,4 @@
-import { getBackendSrv } from '@grafana/runtime';
+import { config, getBackendSrv } from '@grafana/runtime';
import { GENERAL_FOLDER_UID } from 'app/features/search/constants';
import { getGrafanaSearcher, NestedFolderDTO } from 'app/features/search/service';
import { queryResultToViewItem } from 'app/features/search/service/utils';
@@ -12,6 +12,10 @@ export async function listFolders(
page = 1,
pageSize = PAGE_SIZE
): Promise {
+ if (parentUID && !config.featureToggles.nestedFolders) {
+ return [];
+ }
+
const backendSrv = getBackendSrv();
const folders = await backendSrv.get('/api/folders', {
diff --git a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
index 315b2feeff6..a3478cb4669 100644
--- a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
+++ b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx
@@ -1,11 +1,11 @@
import { css } from '@emotion/css';
-import React from 'react';
+import React, { useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
-import { reportInteraction } from '@grafana/runtime';
-import { Button, useStyles2 } from '@grafana/ui';
+import { config, reportInteraction } from '@grafana/runtime';
+import { Button, Tooltip, useStyles2 } from '@grafana/ui';
import appEvents from 'app/core/app_events';
-import { Trans } from 'app/core/internationalization';
+import { t, Trans } from 'app/core/internationalization';
import { useSearchStateManager } from 'app/features/search/state/SearchStateManager';
import { useDispatch } from 'app/types';
import { ShowModalReactEvent } from 'app/types/events';
@@ -27,6 +27,12 @@ export function BrowseActions() {
const [moveItems] = useMoveItemsMutation();
const [, stateManager] = useSearchStateManager();
+ // Folders can only be moved if nested folders is enabled
+ const moveIsInvalid = useMemo(
+ () => !config.featureToggles.nestedFolders && Object.values(selectedItems.folder).some((v) => v),
+ [selectedItems]
+ );
+
const isSearching = stateManager.hasSearchFilters();
const onActionComplete = () => {
@@ -74,11 +80,21 @@ export function BrowseActions() {
);
};
+ const moveButton = (
+
+ );
+
return (
-
+ {moveIsInvalid ? (
+
+ {moveButton}
+
+ ) : (
+ moveButton
+ )}