From 4c5ac7939900ce8e7add05e3cd9d50e218d26e88 Mon Sep 17 00:00:00 2001 From: Roberto Jimenez Sanchez Date: Tue, 2 Dec 2025 19:00:59 +0100 Subject: [PATCH] feat(browse-dashboards): select all dashboards when folder is selected - Add selectFolderWithAllDashboards async thunk to recursively collect all dashboards - Update BrowseView to use the new thunk when selecting folders - When a folder is selected, all dashboards in that folder and subfolders are automatically selected - Similar behavior to folder export functionality --- .../components/BrowseView.tsx | 9 ++- .../browse-dashboards/state/actions.ts | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index b28204f4fe2..7c51b68282d 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -22,6 +22,7 @@ import { useLoadNextChildrenPage, rootItemsSelector, } from '../state/hooks'; +import { selectFolderWithAllDashboards } from '../state/actions'; import { setFolderOpenState, setItemSelectionState, setAllSelection } from '../state/slice'; import { BrowseDashboardsState, DashboardTreeSelection, SelectionState, BrowseDashboardsPermissions } from '../types'; @@ -81,7 +82,13 @@ export function BrowseView({ folderUID, width, height, permissions, isReadOnlyRe const handleItemSelectionChange = useCallback( (item: DashboardViewItem, isSelected: boolean) => { - dispatch(setItemSelectionState({ item, isSelected })); + // If selecting a folder, use the async thunk to collect all dashboards recursively + // When deselecting, the normal reducer will handle deselecting all children + if (item.kind === 'folder') { + dispatch(selectFolderWithAllDashboards({ folderUID: item.uid, isSelected })); + } else { + dispatch(setItemSelectionState({ item, isSelected })); + } }, [dispatch] ); diff --git a/public/app/features/browse-dashboards/state/actions.ts b/public/app/features/browse-dashboards/state/actions.ts index f738493b342..e0dc4674a01 100644 --- a/public/app/features/browse-dashboards/state/actions.ts +++ b/public/app/features/browse-dashboards/state/actions.ts @@ -5,6 +5,7 @@ import { createAsyncThunk } from 'app/types/store'; import { listDashboards, listFolders, PAGE_SIZE } from '../api/services'; import { DashboardViewItemWithUIItems, UIDashboardViewItem } from '../types'; +import { setItemSelectionState } from './slice'; import { findItem } from './utils'; interface FetchNextChildrenPageArgs { @@ -88,6 +89,63 @@ export const refetchChildren = createAsyncThunk( } ); +export const selectFolderWithAllDashboards = createAsyncThunk( + 'browseDashboards/selectFolderWithAllDashboards', + async ( + { folderUID, isSelected }: { folderUID: string; isSelected: boolean }, + { dispatch, getState } + ) => { + const state = getState().browseDashboards; + + // Find the folder item to get its parentUID and managedBy + const folderItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, folderUID); + + if (!isSelected) { + // When deselecting, use the normal action - it will handle deselecting all children recursively + dispatch(setItemSelectionState({ + item: { + kind: 'folder', + uid: folderUID, + parentUID: folderItem?.parentUID, + managedBy: folderItem?.managedBy + }, + isSelected: false + })); + return; + } + + // When selecting, collect all dashboards recursively + const { collectAllDashboardsUnderFolder } = await import('app/features/provisioning/utils/collectFolderDashboards'); + const dashboardUIDs = await collectAllDashboardsUnderFolder(folderUID); + + // First, select the folder itself + dispatch(setItemSelectionState({ + item: { + kind: 'folder', + uid: folderUID, + parentUID: folderItem?.parentUID, + managedBy: folderItem?.managedBy + }, + isSelected: true + })); + + // Then select all dashboards found + // We need to get the parentUID for each dashboard from the state + for (const dashboardUID of dashboardUIDs) { + const dashboardItem = findItem(state.rootItems?.items ?? [], state.childrenByParentUID, dashboardUID); + dispatch(setItemSelectionState({ + item: { + kind: 'dashboard', + uid: dashboardUID, + parentUID: dashboardItem?.parentUID, + managedBy: dashboardItem?.managedBy + }, + isSelected: true + })); + } + } +); + export const fetchNextChildrenPage = createAsyncThunk( 'browseDashboards/fetchNextChildrenPage', async (