diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 76169d3d8ab..c67fe825014 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -46,6 +46,7 @@ const BrowseDashboardsPage = memo(({ match }: Props) => { dispatch( setAllSelection({ isSelected: false, + folderUID: undefined, }) ); }, [dispatch, folderUID, stateManager]); diff --git a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx index 2d6c6c396fe..a42c5ef9f97 100644 --- a/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx +++ b/public/app/features/browse-dashboards/components/BrowseActions/BrowseActions.tsx @@ -40,7 +40,7 @@ export function BrowseActions() { const isSearching = stateManager.hasSearchFilters(); const onActionComplete = (parentsToRefresh: Set) => { - dispatch(setAllSelection({ isSelected: false })); + dispatch(setAllSelection({ isSelected: false, folderUID: undefined })); if (isSearching) { // Redo search query diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index c1236db1378..4ebeaedd37c 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -136,7 +136,7 @@ export function BrowseView({ folderUID, width, height, canSelect }: BrowseViewPr height={height} isSelected={isSelected} onFolderClick={handleFolderClick} - onAllSelectionChange={(newState) => dispatch(setAllSelection({ isSelected: newState }))} + onAllSelectionChange={(newState) => dispatch(setAllSelection({ isSelected: newState, folderUID }))} onItemSelectionChange={handleItemSelectionChange} isItemLoaded={isItemLoaded} requestLoadMore={handleLoadMore} diff --git a/public/app/features/browse-dashboards/components/SearchView.tsx b/public/app/features/browse-dashboards/components/SearchView.tsx index 1c4653de2a9..f2e4b2d19de 100644 --- a/public/app/features/browse-dashboards/components/SearchView.tsx +++ b/public/app/features/browse-dashboards/components/SearchView.tsx @@ -46,7 +46,7 @@ export function SearchView({ width, height, canSelect }: SearchViewProps) { ); const clearSelection = useCallback(() => { - dispatch(setAllSelection({ isSelected: false })); + dispatch(setAllSelection({ isSelected: false, folderUID: undefined })); }, [dispatch]); const handleItemSelectionChange = useCallback( diff --git a/public/app/features/browse-dashboards/state/reducers.test.ts b/public/app/features/browse-dashboards/state/reducers.test.ts index 060a8843d6d..e90b484c129 100644 --- a/public/app/features/browse-dashboards/state/reducers.test.ts +++ b/public/app/features/browse-dashboards/state/reducers.test.ts @@ -324,15 +324,15 @@ describe('browse-dashboards reducers', () => { }); describe('setAllSelection', () => { - it('selects all loaded items', () => { - const state = createInitialState(); + let seed = 1; + const topLevelDashboard = wellFormedDashboard(seed++).item; + const topLevelFolder = wellFormedFolder(seed++).item; + const childDashboard = wellFormedDashboard(seed++, {}, { parentUID: topLevelFolder.uid }).item; + const childFolder = wellFormedFolder(seed++, {}, { parentUID: topLevelFolder.uid }).item; + const grandchildDashboard = wellFormedDashboard(seed++, {}, { parentUID: childFolder.uid }).item; - let seed = 1; - const topLevelDashboard = wellFormedDashboard(seed++).item; - const topLevelFolder = wellFormedFolder(seed++).item; - const childDashboard = wellFormedDashboard(seed++, {}, { parentUID: topLevelFolder.uid }).item; - const childFolder = wellFormedFolder(seed++, {}, { parentUID: topLevelFolder.uid }).item; - const grandchildDashboard = wellFormedDashboard(seed++, {}, { parentUID: childFolder.uid }).item; + it('selects all items in the root folder', () => { + const state = createInitialState(); state.rootItems = fullyLoadedViewItemCollection([topLevelFolder, topLevelDashboard]); state.childrenByParentUID[topLevelFolder.uid] = fullyLoadedViewItemCollection([childDashboard, childFolder]); @@ -341,7 +341,7 @@ describe('browse-dashboards reducers', () => { state.selectedItems.folder[childFolder.uid] = false; state.selectedItems.dashboard[grandchildDashboard.uid] = true; - setAllSelection(state, { type: 'setAllSelection', payload: { isSelected: true } }); + setAllSelection(state, { type: 'setAllSelection', payload: { isSelected: true, folderUID: undefined } }); expect(state.selectedItems).toEqual({ $all: true, @@ -358,16 +358,9 @@ describe('browse-dashboards reducers', () => { }); }); - it('deselects all items', () => { + it('selects all items when viewing a folder', () => { const state = createInitialState(); - let seed = 1; - const topLevelDashboard = wellFormedDashboard(seed++).item; - const topLevelFolder = wellFormedFolder(seed++).item; - const childDashboard = wellFormedDashboard(seed++, {}, { parentUID: topLevelFolder.uid }).item; - const childFolder = wellFormedFolder(seed++, {}, { parentUID: topLevelFolder.uid }).item; - const grandchildDashboard = wellFormedDashboard(seed++, {}, { parentUID: childFolder.uid }).item; - state.rootItems = fullyLoadedViewItemCollection([topLevelFolder, topLevelDashboard]); state.childrenByParentUID[topLevelFolder.uid] = fullyLoadedViewItemCollection([childDashboard, childFolder]); state.childrenByParentUID[childFolder.uid] = fullyLoadedViewItemCollection([grandchildDashboard]); @@ -375,7 +368,32 @@ describe('browse-dashboards reducers', () => { state.selectedItems.folder[childFolder.uid] = false; state.selectedItems.dashboard[grandchildDashboard.uid] = true; - setAllSelection(state, { type: 'setAllSelection', payload: { isSelected: false } }); + setAllSelection(state, { type: 'setAllSelection', payload: { isSelected: true, folderUID: topLevelFolder.uid } }); + + expect(state.selectedItems).toEqual({ + $all: true, + dashboard: { + [childDashboard.uid]: true, + [grandchildDashboard.uid]: true, + }, + folder: { + [childFolder.uid]: true, + }, + panel: {}, + }); + }); + + it('deselects all items', () => { + const state = createInitialState(); + + state.rootItems = fullyLoadedViewItemCollection([topLevelFolder, topLevelDashboard]); + state.childrenByParentUID[topLevelFolder.uid] = fullyLoadedViewItemCollection([childDashboard, childFolder]); + state.childrenByParentUID[childFolder.uid] = fullyLoadedViewItemCollection([grandchildDashboard]); + + state.selectedItems.folder[childFolder.uid] = false; + state.selectedItems.dashboard[grandchildDashboard.uid] = true; + + setAllSelection(state, { type: 'setAllSelection', payload: { isSelected: false, folderUID: undefined } }); // Deselecting only sets selection = false for things already selected expect(state.selectedItems).toEqual({ diff --git a/public/app/features/browse-dashboards/state/reducers.ts b/public/app/features/browse-dashboards/state/reducers.ts index ef9fa2d0be1..35bdca99ce8 100644 --- a/public/app/features/browse-dashboards/state/reducers.ts +++ b/public/app/features/browse-dashboards/state/reducers.ts @@ -129,8 +129,11 @@ export function setItemSelectionState( state.selectedItems.$all = state.rootItems?.items?.every((v) => state.selectedItems[v.kind][v.uid]) ?? false; } -export function setAllSelection(state: BrowseDashboardsState, action: PayloadAction<{ isSelected: boolean }>) { - const { isSelected } = action.payload; +export function setAllSelection( + state: BrowseDashboardsState, + action: PayloadAction<{ isSelected: boolean; folderUID: string | undefined }> +) { + const { isSelected, folderUID: folderUIDArg } = action.payload; state.selectedItems.$all = isSelected; @@ -141,17 +144,27 @@ export function setAllSelection(state: BrowseDashboardsState, action: PayloadAct // redux, so we just need to iterate over the selected items to flip them to false if (isSelected) { - for (const folderUID in state.childrenByParentUID) { - const collection = state.childrenByParentUID[folderUID]; + // Recursively select the children of the folder in view + function selectChildrenOfFolder(folderUID: string | undefined) { + const collection = folderUID ? state.childrenByParentUID[folderUID] : state.rootItems; - for (const child of collection?.items ?? []) { + // Bail early if the collection isn't found (not loaded yet) + if (!collection) { + return; + } + + for (const child of collection.items) { state.selectedItems[child.kind][child.uid] = isSelected; + + if (child.kind !== 'folder') { + continue; + } + + selectChildrenOfFolder(child.uid); } } - for (const child of state.rootItems?.items ?? []) { - state.selectedItems[child.kind][child.uid] = isSelected; - } + selectChildrenOfFolder(folderUIDArg); } else { // if deselecting only need to loop over what we've already selected for (const kind in state.selectedItems) {