diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index e03bba4c5ba..6363840274d 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -349,7 +349,7 @@ export const DashNav = React.memo((props) => { // this ensures the component rerenders when the location changes const location = useLocation(); const titleHref = locationUtil.getUrlForPartial(location, { search: 'open' }); - const parentHref = locationUtil.getUrlForPartial(location, { search: 'open', folder: 'current' }); + const parentHref = locationUtil.getUrlForPartial(location, { search: 'open', query: 'folder:current' }); const onGoBack = isFullscreen ? onClose : undefined; if (config.featureToggles.topnav) { diff --git a/public/app/features/search/hooks/useSearchQuery.ts b/public/app/features/search/hooks/useSearchQuery.ts index 33309d86575..55d7543ff6d 100644 --- a/public/app/features/search/hooks/useSearchQuery.ts +++ b/public/app/features/search/hooks/useSearchQuery.ts @@ -1,5 +1,5 @@ import { debounce } from 'lodash'; -import { FormEvent } from 'react'; +import { FormEvent, useEffect } from 'react'; import { SelectableValue } from '@grafana/data'; import { locationService } from '@grafana/runtime'; @@ -16,6 +16,7 @@ import { clearFilters, toggleSort, layoutChange, + initStateFromUrl, } from '../reducers/searchQueryReducer'; import { DashboardQuery, SearchLayout } from '../types'; import { hasFilters } from '../utils'; @@ -26,6 +27,10 @@ export const useSearchQuery = (defaults: Partial) => { const query = useSelector((state) => state.searchQuery); const dispatch = useDispatch(); + useEffect(() => { + dispatch(initStateFromUrl(locationService.getSearchObject())); + }, [dispatch]); + const onQueryChange = (query: string) => { dispatch(queryChange(query)); updateLocation({ query }); diff --git a/public/app/features/search/page/components/SearchView.test.tsx b/public/app/features/search/page/components/SearchView.test.tsx index 704dd66ba33..60d7a3a2038 100644 --- a/public/app/features/search/page/components/SearchView.test.tsx +++ b/public/app/features/search/page/components/SearchView.test.tsx @@ -7,11 +7,10 @@ import { Observable } from 'rxjs'; import { ArrayVector, DataFrame, DataFrameView, FieldType } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { StoreState } from 'app/types'; -import { defaultQuery } from '../../reducers/searchQueryReducer'; +import { initialState } from '../../reducers/searchQueryReducer'; import { DashboardQueryResult, getGrafanaSearcher, QueryResponse } from '../../service'; -import { DashboardSearchItemType, SearchLayout } from '../../types'; +import { DashboardQuery, DashboardSearchItemType, SearchLayout } from '../../types'; import { SearchView, SearchViewProps } from './SearchView'; @@ -29,7 +28,7 @@ jest.mock('@grafana/runtime', () => { }; }); -const setup = (propOverrides?: Partial, storeOverrides?: Partial) => { +const setup = (propOverrides?: Partial, storeOverrides?: Partial) => { const props: SearchViewProps = { showManage: false, includePanels: false, @@ -39,7 +38,7 @@ const setup = (propOverrides?: Partial, storeOverrides?: Partia }; const mockStore = configureMockStore(); - const store = mockStore({ searchQuery: defaultQuery, ...storeOverrides }); + const store = mockStore({ searchQuery: { ...initialState, ...storeOverrides } }); render( @@ -75,22 +74,21 @@ describe('SearchView', () => { beforeEach(() => { config.featureToggles.panelTitleSearch = false; - defaultQuery.layout = SearchLayout.Folders; }); it('does not show checkboxes or manage actions if showManage is false', async () => { - setup(); + setup({}, { layout: SearchLayout.Folders }); await waitFor(() => expect(screen.queryAllByRole('checkbox')).toHaveLength(0)); expect(screen.queryByTestId('manage-actions')).not.toBeInTheDocument(); }); it('shows checkboxes if showManage is true', async () => { - setup({ showManage: true }); + setup({ showManage: true }, { layout: SearchLayout.Folders }); await waitFor(() => expect(screen.queryAllByRole('checkbox')).toHaveLength(2)); }); it('shows the manage actions if show manage is true and the user clicked a checkbox', async () => { - setup({ showManage: true }); + setup({ showManage: true }, { layout: SearchLayout.Folders }); await waitFor(() => userEvent.click(screen.getAllByRole('checkbox')[0])); expect(screen.queryByTestId('manage-actions')).toBeInTheDocument(); @@ -103,10 +101,8 @@ describe('SearchView', () => { view: new DataFrameView({ fields: [], length: 0 }), }); setup(undefined, { - searchQuery: { - ...defaultQuery, - query: 'asdfasdfasdf', - }, + query: 'asdfasdfasdf', + layout: SearchLayout.Folders, }); await waitFor(() => expect(screen.queryByText('No results found for your query.')).toBeInTheDocument()); expect(screen.getByRole('button', { name: 'Clear search and filters' })).toBeInTheDocument(); @@ -115,15 +111,15 @@ describe('SearchView', () => { describe('include panels', () => { it('should be enabled when layout is list', async () => { config.featureToggles.panelTitleSearch = true; - defaultQuery.layout = SearchLayout.List; - setup(); + setup({}, { layout: SearchLayout.List }); await waitFor(() => expect(screen.getByLabelText(/include panels/i)).toBeInTheDocument()); expect(screen.getByTestId('include-panels')).toBeEnabled(); }); + it('should be disabled when layout is folder', async () => { config.featureToggles.panelTitleSearch = true; - setup(); + setup({}, { layout: SearchLayout.Folders }); await waitFor(() => expect(screen.getByLabelText(/include panels/i)).toBeInTheDocument()); expect(screen.getByTestId('include-panels')).toBeDisabled(); diff --git a/public/app/features/search/reducers/searchQueryReducer.ts b/public/app/features/search/reducers/searchQueryReducer.ts index 08131213b0c..a2dc006c33f 100644 --- a/public/app/features/search/reducers/searchQueryReducer.ts +++ b/public/app/features/search/reducers/searchQueryReducer.ts @@ -1,17 +1,17 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { SelectableValue } from '@grafana/data'; +import { SelectableValue, UrlQueryMap } from '@grafana/data'; import { locationService } from '@grafana/runtime'; import { SEARCH_SELECTED_LAYOUT } from '../constants'; -import { DashboardQuery, SearchQueryParams, SearchLayout } from '../types'; +import { DashboardQuery, SearchLayout, SearchQueryParams } from '../types'; import { parseRouteParams } from '../utils'; -export const defaultQuery: DashboardQuery = { +export const initialState: DashboardQuery = { query: '', tag: [], - starred: false, sort: null, + starred: false, layout: SearchLayout.Folders, prevSort: null, }; @@ -25,8 +25,8 @@ export const defaultQueryParams: SearchQueryParams = { }; const queryParams = parseRouteParams(locationService.getSearchObject()); -const initialState = { ...defaultQuery, ...queryParams }; const selectedLayout = localStorage.getItem(SEARCH_SELECTED_LAYOUT) as SearchLayout; + if (!queryParams.layout?.length && selectedLayout?.length) { initialState.layout = selectedLayout; } @@ -35,6 +35,10 @@ const searchQuerySlice = createSlice({ name: 'searchQuery', initialState, reducers: { + initStateFromUrl(state, action: PayloadAction) { + const queryParams = parseRouteParams(action.payload); + Object.assign(state, queryParams); + }, queryChange: (state, action: PayloadAction) => { state.query = action.payload; }, @@ -99,6 +103,7 @@ export const { clearFilters, toggleSort, layoutChange, + initStateFromUrl, } = searchQuerySlice.actions; export const searchQueryReducer = searchQuerySlice.reducer;