From cf4b89e56bab6f53bb994bc64fa6cdd8f249cd4d Mon Sep 17 00:00:00 2001 From: Robb Hamilton Date: Tue, 17 Mar 2026 08:27:33 -0400 Subject: [PATCH] OCPBUGS-78390: Reset pagination page when changing namespace This commit adds namespace change detection to reset pagination, ensuring users see page 1 when switching namespaces. Co-Authored-By: Claude Sonnet 4.5 --- .../data-view/useConsoleDataViewData.tsx | 27 ++++++++++---- frontend/public/actions/__tests__/ui.spec.ts | 35 +++++++++++++++++++ frontend/public/actions/ui.ts | 11 +++++- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/frontend/packages/console-app/src/components/data-view/useConsoleDataViewData.tsx b/frontend/packages/console-app/src/components/data-view/useConsoleDataViewData.tsx index 28911105dba..db1547dbb36 100644 --- a/frontend/packages/console-app/src/components/data-view/useConsoleDataViewData.tsx +++ b/frontend/packages/console-app/src/components/data-view/useConsoleDataViewData.tsx @@ -1,13 +1,19 @@ import * as React from 'react'; -import { useDataViewPagination, DataViewTh } from '@patternfly/react-data-view'; -import { SortByDirection, ThProps } from '@patternfly/react-table'; +import type { DataViewTh } from '@patternfly/react-data-view'; +import { useDataViewPagination } from '@patternfly/react-data-view'; +import type { ThProps } from '@patternfly/react-table'; +import { SortByDirection } from '@patternfly/react-table'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useSearchParams } from 'react-router-dom-v5-compat'; -import { TableColumn, RowProps } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; +import type { + TableColumn, + RowProps, +} from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { useActiveColumns } from '@console/internal/components/factory/Table/active-columns-hook'; import { sortResourceByValue } from '@console/internal/components/factory/Table/sort'; -import { ConsoleDataViewColumn, GetDataViewRows, ResourceFilters } from './types'; +import { useActiveNamespace } from '@console/shared/src/hooks/useActiveNamespace'; +import type { ConsoleDataViewColumn, GetDataViewRows, ResourceFilters } from './types'; import { useConsoleDataViewSort, getSortByDirection } from './useConsoleDataViewSort'; const isDataViewConfigurableColumn = ( @@ -40,6 +46,8 @@ export const useConsoleDataViewData = < const { t } = useTranslation(); const [searchParams, setSearchParams] = useSearchParams(); const prevFiltersRef = React.useRef(filters); + const [activeNamespace] = useActiveNamespace(); + const prevNamespaceRef = React.useRef(activeNamespace); const pagination = useDataViewPagination({ perPage: 50, @@ -47,13 +55,17 @@ export const useConsoleDataViewData = < setSearchParams, }); - // Reset pagination to page 1 when filters change + // Reset pagination to page 1 when filters or namespace change React.useEffect(() => { const currentFilters = filters; const prevFilters = prevFiltersRef.current; const filtersChanged = !_.isEqual(currentFilters, prevFilters); - if (filtersChanged && pagination.page > 1) { + const currentNamespace = activeNamespace; + const prevNamespace = prevNamespaceRef.current; + const namespaceChanged = currentNamespace !== prevNamespace; + + if ((filtersChanged || namespaceChanged) && pagination.page > 1) { setSearchParams((prev) => { const newParams = new URLSearchParams(prev); newParams.set('page', '1'); @@ -62,7 +74,8 @@ export const useConsoleDataViewData = < } prevFiltersRef.current = currentFilters; - }, [filters, pagination.page, setSearchParams]); + prevNamespaceRef.current = currentNamespace; + }, [filters, activeNamespace, pagination.page, setSearchParams]); const [activeColumns] = useActiveColumns({ columns, diff --git a/frontend/public/actions/__tests__/ui.spec.ts b/frontend/public/actions/__tests__/ui.spec.ts index 08c2ceb6fef..52a6594a129 100644 --- a/frontend/public/actions/__tests__/ui.spec.ts +++ b/frontend/public/actions/__tests__/ui.spec.ts @@ -23,6 +23,41 @@ describe('ui-actions', () => { expect(UIActions.formatNamespaceRoute(t[0], t[1])).toEqual(t[2]); }); }); + + it('removes page query param when namespace changes', () => { + const location = { search: '?page=2&perPage=50', hash: '' }; + expect(UIActions.formatNamespaceRoute('bar', '/k8s/ns/foo/pods', location)).toEqual( + '/k8s/ns/bar/pods?perPage=50', + ); + }); + + it('removes page query param when switching from all-namespaces', () => { + const location = { search: '?page=2&perPage=50', hash: '' }; + expect(UIActions.formatNamespaceRoute('bar', '/k8s/all-namespaces/pods', location)).toEqual( + '/k8s/ns/bar/pods?perPage=50', + ); + }); + + it('preserves all query params when namespace does not change', () => { + const location = { search: '?page=2&perPage=50&sortBy=Name', hash: '' }; + expect(UIActions.formatNamespaceRoute('foo', '/k8s/ns/foo/pods', location)).toEqual( + '/k8s/ns/foo/pods?page=2&perPage=50&sortBy=Name', + ); + }); + + it('preserves hash when removing page param', () => { + const location = { search: '?page=2&perPage=50', hash: '#section' }; + expect(UIActions.formatNamespaceRoute('bar', '/k8s/ns/foo/pods', location)).toEqual( + '/k8s/ns/bar/pods?perPage=50#section', + ); + }); + + it('handles empty query string after removing page param', () => { + const location = { search: '?page=2', hash: '' }; + expect(UIActions.formatNamespaceRoute('bar', '/k8s/ns/foo/pods', location)).toEqual( + '/k8s/ns/bar/pods', + ); + }); }); describe('setActiveNamespace', () => { diff --git a/frontend/public/actions/ui.ts b/frontend/public/actions/ui.ts index ee3c583c411..d7fbea07664 100644 --- a/frontend/public/actions/ui.ts +++ b/frontend/public/actions/ui.ts @@ -145,7 +145,16 @@ export const formatNamespaceRoute = ( } if (location) { - path += `${location.search}${location.hash}`; + // When namespace changes, reset page to 1 but preserve perPage preference + if (previousNS !== activeNamespace) { + const searchParams = new URLSearchParams(location.search); + searchParams.delete('page'); + const search = searchParams.toString(); + path += search ? `?${search}` : ''; + path += location.hash; + } else { + path += `${location.search}${location.hash}`; + } } return path;