diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts index 5ac2a4328b00a..685fca97b136c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts @@ -16,12 +16,4 @@ interface ServerReturnedPolicyListData { }; } -interface UserPaginatedPolicyListTable { - type: 'userPaginatedPolicyListTable'; - payload: { - pageSize: number; - pageIndex: number; - }; -} - -export type PolicyListAction = ServerReturnedPolicyListData | UserPaginatedPolicyListTable; +export type PolicyListAction = ServerReturnedPolicyListData; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts index ae4a0868a68fe..a1278b12808fd 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyListState } from '../../types'; +import { EndpointAppLocation, PolicyListState } from '../../types'; import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; import { AppAction } from '../action'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { CoreStart } from 'kibana/public'; -import { selectIsLoading } from './selectors'; +import { isOnPolicyListPage, selectIsLoading } from './selectors'; describe('policy list store concerns', () => { const sleep = () => new Promise(resolve => setTimeout(resolve, 1000)); @@ -30,29 +30,56 @@ describe('policy list store concerns', () => { dispatch = store.dispatch; }); - test('it sets `isLoading` when `userNavigatedToPage`', async () => { - expect(selectIsLoading(getState())).toBe(false); - dispatch({ type: 'userNavigatedToPage', payload: 'policyListPage' }); - expect(selectIsLoading(getState())).toBe(true); - await sleep(); - expect(selectIsLoading(getState())).toBe(false); + test('it does nothing on `userChangedUrl` if pathname is NOT `/policy`', async () => { + const state = getState(); + expect(isOnPolicyListPage(state)).toBe(false); + dispatch({ + type: 'userChangedUrl', + payload: { + pathname: '/foo', + search: '', + hash: '', + } as EndpointAppLocation, + }); + expect(getState()).toEqual(state); }); - test('it sets `isLoading` when `userPaginatedPolicyListTable`', async () => { + test('it sets `isOnPage` when `userChangedUrl` with pathname `/policy`', async () => { + dispatch({ + type: 'userChangedUrl', + payload: { + pathname: '/policy', + search: '', + hash: '', + } as EndpointAppLocation, + }); + expect(isOnPolicyListPage(getState())).toBe(true); + }); + + test('it sets `isLoading` when `userChangedUrl`', async () => { expect(selectIsLoading(getState())).toBe(false); dispatch({ - type: 'userPaginatedPolicyListTable', + type: 'userChangedUrl', payload: { - pageSize: 10, - pageIndex: 1, - }, + pathname: '/policy', + search: '', + hash: '', + } as EndpointAppLocation, }); expect(selectIsLoading(getState())).toBe(true); await sleep(); expect(selectIsLoading(getState())).toBe(false); }); - test('it resets state on `userNavigatedFromPage` action', async () => { + test('it resets state on `userChnagedUrl` and pathname is NOT `/policy`', async () => { + dispatch({ + type: 'userChangedUrl', + payload: { + pathname: '/policy', + search: '', + hash: '', + } as EndpointAppLocation, + }); dispatch({ type: 'serverReturnedPolicyListData', payload: { @@ -62,10 +89,18 @@ describe('policy list store concerns', () => { total: 200, }, }); - dispatch({ type: 'userNavigatedFromPage', payload: 'policyListPage' }); + dispatch({ + type: 'userChangedUrl', + payload: { + pathname: '/foo', + search: '', + hash: '', + } as EndpointAppLocation, + }); expect(getState()).toEqual({ policyItems: [], isLoading: false, + isOnPage: false, pageIndex: 0, pageSize: 10, total: 0, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts index f8e2b7d07c389..0f556c90aeacd 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/middleware.ts @@ -4,27 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import { MiddlewareFactory, PolicyListState } from '../../types'; +import { isOnPolicyListPage } from './selectors'; + +const PAGE_SIZES = Object.freeze([10, 20, 50]); export const policyListMiddlewareFactory: MiddlewareFactory = coreStart => { return ({ getState, dispatch }) => next => async action => { next(action); - if ( - (action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') || - action.type === 'userPaginatedPolicyListTable' - ) { - const state = getState(); - let pageSize: number; - let pageIndex: number; - - if (action.type === 'userPaginatedPolicyListTable') { - pageSize = action.payload.pageSize; - pageIndex = action.payload.pageIndex; - } else { - pageSize = state.pageSize; - pageIndex = state.pageIndex; - } + if (action.type === 'userChangedUrl' && isOnPolicyListPage(getState())) { + const { pageSize, pageIndex } = getPaginationFromUrlSearchParams(action.payload.search); // Need load data from API and remove fake data below // Refactor tracked via: https://github.com/elastic/endpoint-app-team/issues/150 @@ -43,3 +34,23 @@ export const policyListMiddlewareFactory: MiddlewareFactory = c } }; }; + +const getPaginationFromUrlSearchParams = (searchParams: string) => { + const query = parse(searchParams); + const pagination = { + pageIndex: Number(query.page_index ?? 0), + pageSize: Number(query.page_size ?? 10), + }; + + // If pageIndex is not a valid positive integer, set it to 0 + if (!Number.isFinite(pagination.pageIndex) || pagination.pageIndex < 0) { + pagination.pageIndex = 0; + } + + // if pageSize is not one of the expected page sizes, reset it to 10 + if (!PAGE_SIZES.includes(pagination.pageSize)) { + pagination.pageSize = 10; + } + + return pagination; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts index 77f536d413ae3..8b3f2b27d82b3 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts @@ -7,6 +7,7 @@ import { Reducer } from 'redux'; import { PolicyListState } from '../../types'; import { AppAction } from '../action'; +import { isOnPolicyListPage } from './selectors'; const initialPolicyListState = (): PolicyListState => { return { @@ -15,6 +16,7 @@ const initialPolicyListState = (): PolicyListState => { pageIndex: 0, pageSize: 10, total: 0, + isOnPage: false, }; }; @@ -30,17 +32,15 @@ export const policyListReducer: Reducer = ( }; } - if ( - action.type === 'userPaginatedPolicyListTable' || - (action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') - ) { + if (action.type === 'userChangedUrl' && action.payload.pathname === '/policy') { return { ...state, isLoading: true, + isOnPage: true, }; } - if (action.type === 'userNavigatedFromPage' && action.payload === 'policyListPage') { + if (action.type === 'userChangedUrl' && isOnPolicyListPage(state)) { return initialPolicyListState(); } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts index b9c2edbf5d55b..8f6f8c624e93e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts @@ -15,3 +15,5 @@ export const selectPageSize = (state: PolicyListState) => state.pageSize; export const selectTotal = (state: PolicyListState) => state.total; export const selectIsLoading = (state: PolicyListState) => state.isLoading; + +export const isOnPolicyListPage = (state: PolicyListState) => state.isOnPage; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index d07521d09a119..02d438997bdce 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -55,6 +55,8 @@ export interface PolicyListState { pageIndex: number; /** data is being retrieved from server */ isLoading: boolean; + /** is user on the Policy List page (route) */ + isOnPage: boolean; } export interface GlobalState { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 75ffa5e8806e9..b54c4d79e2044 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -26,9 +26,8 @@ import { FormattedNumber, FormattedRelative, } from '@kbn/i18n/react'; -import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import { usePageId } from '../use_page_id'; +import { useHistory, useLocation } from 'react-router-dom'; import { selectIsLoading, selectPageIndex, @@ -37,7 +36,6 @@ import { selectTotal, } from '../../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; -import { PolicyListAction } from '../../store/policy_list'; import { PolicyData } from '../../types'; import { TruncateText } from '../../components/truncate_text'; @@ -85,9 +83,9 @@ const renderFormattedNumber = (value: number, _item: PolicyData) => ( ); export const PolicyList = React.memo(() => { - usePageId('policyListPage'); + const history = useHistory(); + const location = useLocation(); - const dispatch = useDispatch<(action: PolicyListAction) => void>(); const policyItems = usePolicyListSelector(selectPolicyItems); const pageIndex = usePolicyListSelector(selectPageIndex); const pageSize = usePolicyListSelector(selectPageSize); @@ -106,15 +104,9 @@ export const PolicyList = React.memo(() => { const handleTableChange = useCallback( ({ page: { index, size } }: TableChangeCallbackArguments) => { - dispatch({ - type: 'userPaginatedPolicyListTable', - payload: { - pageIndex: index, - pageSize: size, - }, - }); + history.push(`${location.pathname}?page_index=${index}&page_size=${size}`); }, - [dispatch] + [history, location.pathname] ); const columns: Array> = useMemo(