From 531b6f29fd87cca3d93edca66a06dcbfd6cf5258 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Mon, 15 May 2023 13:36:37 +0100 Subject: [PATCH] [v10.0.x] Nested folders: Clear selection state when navigating to a different folder (#68455) Nested folders: Clear selection state when navigating to a different folder (#68438) * clear selection state when navigating to a different folder * consolidate useEffects (cherry picked from commit 7e3c68d3c1a292bd2f93e6cebf979f1f5b659384) Co-authored-by: Ashley Harrison --- .../BrowseDashboardsPage.test.tsx | 23 +++++++++++++++++-- .../BrowseDashboardsPage.tsx | 15 ++++++++++-- .../fixtures/dashboardsTreeItem.fixture.ts | 4 +++- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx index 3cd8b448e51..4c7a4741afb 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.test.tsx @@ -1,4 +1,4 @@ -import { render as rtlRender, screen } from '@testing-library/react'; +import { render as rtlRender, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React, { ComponentProps } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -10,7 +10,7 @@ import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps import BrowseDashboardsPage, { Props } from './BrowseDashboardsPage'; import { wellFormedTree } from './fixtures/dashboardsTreeItem.fixture'; import * as permissions from './permissions'; -const [mockTree, { dashbdD }] = wellFormedTree(); +const [mockTree, { dashbdD, folderA }] = wellFormedTree(); jest.mock('react-virtualized-auto-sizer', () => { return { @@ -98,4 +98,23 @@ describe('browse-dashboards BrowseDashboardsPage', () => { expect(screen.getByRole('button', { name: 'Move' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); }); + + it('navigating into a child item resets the selected state', async () => { + render(); + + const checkbox = await screen.findByTestId(selectors.pages.BrowseDashbards.table.checkbox(folderA.item.uid)); + await userEvent.click(checkbox); + + // Check the actions are now visible + expect(screen.getByRole('button', { name: 'Move' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('link', { name: folderA.item.title })); + + // Check the actions are no longer visible + waitFor(() => { + expect(screen.queryByRole('button', { name: 'Move' })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument(); + }); + }); }); diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index ac10735a8a7..53ed0989428 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data'; import { FilterInput, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; +import { useDispatch } from 'app/types'; import { buildNavModel } from '../folders/state/navModel'; import { useSearchStateManager } from '../search/state/SearchStateManager'; @@ -18,7 +19,7 @@ import { BrowseView } from './components/BrowseView'; import { CreateNewButton } from './components/CreateNewButton'; import { SearchView } from './components/SearchView'; import { getFolderPermissions } from './permissions'; -import { useHasSelection } from './state'; +import { setAllSelection, useHasSelection } from './state'; export interface BrowseDashboardsPageRouteParams { uid?: string; @@ -31,12 +32,22 @@ export interface Props extends GrafanaRouteComponentProps { const { uid: folderUID } = match.params; + const dispatch = useDispatch(); const styles = useStyles2(getStyles); const [searchState, stateManager] = useSearchStateManager(); const isSearching = stateManager.hasSearchFilters(); - useEffect(() => stateManager.initStateFromUrl(folderUID), [folderUID, stateManager]); + useEffect(() => { + stateManager.initStateFromUrl(folderUID); + + // Clear selected state when folderUID changes + dispatch( + setAllSelection({ + isSelected: false, + }) + ); + }, [dispatch, folderUID, stateManager]); useEffect(() => { // Clear the search results when we leave SearchView to prevent old results flashing diff --git a/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts b/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts index a40bd2264c0..18fcd6139d4 100644 --- a/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts +++ b/public/app/features/browse-dashboards/fixtures/dashboardsTreeItem.fixture.ts @@ -48,12 +48,14 @@ export function wellFormedFolder( itemPartial?: Partial ): DashboardsTreeItem { const random = Chance(seed); + const uid = random.guid(); return { item: { kind: 'folder', title: random.sentence({ words: 3 }), - uid: random.guid(), + uid, + url: `/dashboards/f/${uid}`, ...itemPartial, }, level: 0,