From b59fb972c65c485c3f2811ae415d43a2bb5951f7 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 23 May 2022 12:02:43 +0200 Subject: [PATCH] [Security Solution] Update use_url_state to work with new side nav (#132518) * Fix landing pages browser tab title * Fix new navigation url state * Fix unit tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/url_state/index.test.tsx | 69 +++++++++++ .../url_state/index_mocked.test.tsx | 111 +++++++++++++++++- .../components/url_state/use_url_state.tsx | 23 +++- 3 files changed, 197 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx index faf3fe10da079..cb49215ee8c9c 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx @@ -25,6 +25,11 @@ import { UrlStateContainerPropTypes } from './types'; import { useUrlStateHooks } from './use_url_state'; import { waitFor } from '@testing-library/react'; import { useLocation } from 'react-router-dom'; +import { updateAppLinks } from '../../links'; +import { getAppLinks } from '../../links/app_links'; +import { StartPlugins } from '../../../types'; +import { allowedExperimentalValues } from '../../../../common/experimental_features'; +import { coreMock } from '@kbn/core/public/mocks'; let mockProps: UrlStateContainerPropTypes; @@ -78,10 +83,36 @@ jest.mock('react-router-dom', () => { }; }); +const mockedUseIsGroupedNavigationEnabled = jest.fn(); +jest.mock('../navigation/helpers', () => ({ + useIsGroupedNavigationEnabled: () => mockedUseIsGroupedNavigationEnabled(), +})); + describe('UrlStateContainer', () => { + beforeAll(async () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(false); + + const appLinks = await getAppLinks(coreMock.createStart(), {} as StartPlugins); + updateAppLinks(appLinks, { + experimentalFeatures: allowedExperimentalValues, + capabilities: { + navLinks: {}, + management: {}, + catalogue: {}, + actions: { show: true, crud: true }, + siem: { + show: true, + crud: true, + }, + }, + }); + }); + afterEach(() => { jest.clearAllMocks(); + mockedUseIsGroupedNavigationEnabled.mockReturnValue(false); }); + describe('handleInitialize', () => { describe('URL state updates redux', () => { describe('relative timerange actions are called with correct data on component mount', () => { @@ -226,6 +257,44 @@ describe('UrlStateContainer', () => { expect(mockHistory.replace).not.toHaveBeenCalled(); }); + it("it doesn't update URL state when on admin page and grouped nav disabled", () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(false); + mockProps = getMockPropsObj({ + page: CONSTANTS.unknown, + examplePath: '/administration', + namespaceLower: 'administration', + pageName: SecurityPageName.administration, + detailName: undefined, + }).noSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + + mount( useUrlStateHooks(args)} />); + + expect(mockHistory.replace.mock.calls[0][0].search).toBe('?'); + }); + + it("it doesn't update URL state when on admin page and grouped nav enabled", () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(true); + mockProps = getMockPropsObj({ + page: CONSTANTS.unknown, + examplePath: '/dashboards', + namespaceLower: 'dashboards', + pageName: SecurityPageName.dashboardsLanding, + detailName: undefined, + }).noSearch.undefinedQuery; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + + mount( useUrlStateHooks(args)} />); + + expect(mockHistory.replace.mock.calls[0][0].search).toBe('?'); + }); + it('it removes empty AppQuery state from URL', () => { mockProps = { ...getMockProps( diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx index 4063ecdb73935..011621b95a0c4 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx @@ -16,7 +16,12 @@ import { getFilterQuery, getMockPropsObj, mockHistory, testCases } from './test_ import { UrlStateContainerPropTypes } from './types'; import { useUrlStateHooks } from './use_url_state'; import { useLocation } from 'react-router-dom'; -import { MANAGEMENT_PATH } from '../../../../common/constants'; +import { DASHBOARDS_PATH, MANAGEMENT_PATH } from '../../../../common/constants'; +import { getAppLinks } from '../../links/app_links'; +import { StartPlugins } from '../../../types'; +import { updateAppLinks } from '../../links'; +import { allowedExperimentalValues } from '../../../../common/experimental_features'; +import { coreMock } from '@kbn/core/public/mocks'; let mockProps: UrlStateContainerPropTypes; @@ -45,7 +50,31 @@ jest.mock('react-redux', () => { }; }); +const mockedUseIsGroupedNavigationEnabled = jest.fn(); +jest.mock('../navigation/helpers', () => ({ + useIsGroupedNavigationEnabled: () => mockedUseIsGroupedNavigationEnabled(), +})); + describe('UrlStateContainer - lodash.throttle mocked to test update url', () => { + beforeAll(async () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(false); + + const appLinks = await getAppLinks(coreMock.createStart(), {} as StartPlugins); + updateAppLinks(appLinks, { + experimentalFeatures: allowedExperimentalValues, + capabilities: { + navLinks: {}, + management: {}, + catalogue: {}, + actions: { show: true, crud: true }, + siem: { + show: true, + crud: true, + }, + }, + }); + }); + afterEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); @@ -210,7 +239,8 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => }); }); - test("administration page doesn't has query string", () => { + test("administration page doesn't has query string when grouped nav disabled", () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(false); mockProps = getMockPropsObj({ page: CONSTANTS.networkPage, examplePath: '/network', @@ -285,6 +315,83 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => state: '', }); }); + + test("dashboards page doesn't has query string when grouped nav enabled", () => { + mockedUseIsGroupedNavigationEnabled.mockReturnValue(true); + mockProps = getMockPropsObj({ + page: CONSTANTS.networkPage, + examplePath: '/network', + namespaceLower: 'network', + pageName: SecurityPageName.network, + detailName: undefined, + }).noSearch.definedQuery; + + const urlState = { + ...mockProps.urlState, + [CONSTANTS.appQuery]: getFilterQuery(), + [CONSTANTS.timerange]: { + global: { + [CONSTANTS.timerange]: { + from: '2020-07-07T08:20:18.966Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2020-07-08T08:20:18.966Z', + toStr: 'now', + }, + linkTo: ['timeline'], + }, + timeline: { + [CONSTANTS.timerange]: { + from: '2020-07-07T08:20:18.966Z', + fromStr: 'now-24h', + kind: 'relative', + to: '2020-07-08T08:20:18.966Z', + toStr: 'now', + }, + linkTo: ['global'], + }, + }, + }; + + const updatedMockProps = { + ...getMockPropsObj({ + ...mockProps, + page: CONSTANTS.unknown, + examplePath: DASHBOARDS_PATH, + namespaceLower: 'dashboards', + pageName: SecurityPageName.dashboardsLanding, + detailName: undefined, + }).noSearch.definedQuery, + urlState, + }; + + (useLocation as jest.Mock).mockReturnValue({ + pathname: mockProps.pathName, + }); + + const wrapper = mount( + useUrlStateHooks(args)} + /> + ); + + (useLocation as jest.Mock).mockReturnValue({ + pathname: updatedMockProps.pathName, + }); + + wrapper.setProps({ + hookProps: updatedMockProps, + }); + + wrapper.update(); + expect(mockHistory.replace.mock.calls[1][0]).toStrictEqual({ + hash: '', + pathname: DASHBOARDS_PATH, + search: '?', + state: '', + }); + }); }); describe('handleInitialize', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx index 3245d647227ad..e787b3a750e91 100644 --- a/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx +++ b/x-pack/plugins/security_solution/public/common/components/url_state/use_url_state.tsx @@ -40,6 +40,9 @@ import { import { TimelineUrl } from '../../../timelines/store/timeline/model'; import { UrlInputsModel } from '../../store/inputs/model'; import { queryTimelineByIdOnUrlChange } from './query_timeline_by_id_on_url_change'; +import { getLinkInfo } from '../../links'; +import { SecurityPageName } from '../../../app/types'; +import { useIsGroupedNavigationEnabled } from '../navigation/helpers'; function usePrevious(value: PreviousLocationUrlState) { const ref = useRef(value); @@ -62,7 +65,9 @@ export const useUrlStateHooks = ({ const { filterManager, savedQueries } = useKibana().services.data.query; const { pathname: browserPathName } = useLocation(); const prevProps = usePrevious({ pathName, pageName, urlState, search }); + const isGroupedNavEnabled = useIsGroupedNavigationEnabled(); + const linkInfo = pageName ? getLinkInfo(pageName as SecurityPageName) : undefined; const { setInitialStateFromUrl, updateTimeline, updateTimelineIsLoading } = useSetInitialStateFromUrl(); @@ -70,9 +75,10 @@ export const useUrlStateHooks = ({ (type: UrlStateType) => { const urlStateUpdatesToStore: UrlStateToRedux[] = []; const urlStateUpdatesToLocation: ReplaceStateInLocation[] = []; + const skipUrlState = isGroupedNavEnabled ? linkInfo?.skipUrlState : isAdministration(type); // Delete all query strings from URL when the page is security/administration (Manage menu group) - if (isAdministration(type)) { + if (skipUrlState) { ALL_URL_STATE_KEYS.forEach((urlKey: KeyUrlState) => { urlStateUpdatesToLocation.push({ urlStateToReplace: '', @@ -146,6 +152,8 @@ export const useUrlStateHooks = ({ setInitialStateFromUrl, urlState, isFirstPageLoad, + isGroupedNavEnabled, + linkInfo?.skipUrlState, ] ); @@ -159,8 +167,9 @@ export const useUrlStateHooks = ({ if (browserPathName !== pathName) return; const type: UrlStateType = getUrlType(pageName); + const skipUrlState = isGroupedNavEnabled ? linkInfo?.skipUrlState : isAdministration(type); - if (!deepEqual(urlState, prevProps.urlState) && !isFirstPageLoad && !isAdministration(type)) { + if (!deepEqual(urlState, prevProps.urlState) && !isFirstPageLoad && !skipUrlState) { const urlStateUpdatesToLocation: ReplaceStateInLocation[] = ALL_URL_STATE_KEYS.map( (urlKey: KeyUrlState) => ({ urlStateToReplace: getUrlStateKeyValue(urlState, urlKey), @@ -186,11 +195,17 @@ export const useUrlStateHooks = ({ browserPathName, handleInitialize, search, + isGroupedNavEnabled, + linkInfo?.skipUrlState, ]); useEffect(() => { - document.title = `${getTitle(pageName, navTabs)} - Kibana`; - }, [pageName, navTabs]); + if (!isGroupedNavEnabled) { + document.title = `${getTitle(pageName, navTabs)} - Kibana`; + } else { + document.title = `${linkInfo?.title ?? ''} - Kibana`; + } + }, [pageName, navTabs, isGroupedNavEnabled, linkInfo]); useEffect(() => { queryTimelineByIdOnUrlChange({