Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v10.0.x] NestedFolders: Fix select all in folder view selecting items out of folder #69783

Merged
merged 1 commit into from Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -45,6 +45,7 @@ const BrowseDashboardsPage = memo(({ match }: Props) => {
dispatch(
setAllSelection({
isSelected: false,
folderUID: undefined,
})
);
}, [dispatch, folderUID, stateManager]);
Expand Down
Expand Up @@ -38,11 +38,8 @@ export function BrowseActions() {
const isSearching = stateManager.hasSearchFilters();

const onActionComplete = (parentsToRefresh: Set<string | undefined>) => {
dispatch(
setAllSelection({
isSelected: false,
})
);
dispatch(setAllSelection({ isSelected: false, folderUID: undefined }));

if (isSearching) {
// Redo search query
stateManager.doSearchWithDebounce();
Expand Down
Expand Up @@ -128,7 +128,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}
/>
);
Expand Down
Expand Up @@ -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(
Expand Down
52 changes: 35 additions & 17 deletions public/app/features/browse-dashboards/state/reducers.test.ts
Expand Up @@ -252,24 +252,24 @@ describe('browse-dashboards reducers', () => {
});

describe('setAllSelection', () => {
it('selects all loaded items', () => {
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();

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 = [topLevelFolder, topLevelDashboard];
state.childrenByParentUID[topLevelFolder.uid] = [childDashboard, childFolder];
state.childrenByParentUID[childFolder.uid] = [grandchildDashboard];

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,
Expand All @@ -286,15 +286,33 @@ 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 = [topLevelFolder, topLevelDashboard];
state.childrenByParentUID[topLevelFolder.uid] = [childDashboard, childFolder];
state.childrenByParentUID[childFolder.uid] = [grandchildDashboard];

state.selectedItems.folder[childFolder.uid] = false;
state.selectedItems.dashboard[grandchildDashboard.uid] = true;

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 = [topLevelFolder, topLevelDashboard];
state.childrenByParentUID[topLevelFolder.uid] = [childDashboard, childFolder];
Expand All @@ -303,7 +321,7 @@ 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: false, folderUID: undefined } });

// Deselecting only sets selection = false for things already selected
expect(state.selectedItems).toEqual({
Expand Down
29 changes: 21 additions & 8 deletions public/app/features/browse-dashboards/state/reducers.ts
Expand Up @@ -90,8 +90,11 @@ export function setItemSelectionState(
state.selectedItems.$all = state.rootItems?.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;

Expand All @@ -102,17 +105,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 children = 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;

// Bail early if the collection isn't found (not loaded yet)
if (!collection) {
return;
}

for (const child of children) {
for (const child of collection) {
state.selectedItems[child.kind][child.uid] = isSelected;

if (child.kind !== 'folder') {
continue;
}

selectChildrenOfFolder(child.uid);
}
}

for (const child of state.rootItems ?? []) {
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) {
Expand Down