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 + )}