diff --git a/awx/ui_next/src/components/PaginatedDataList/PaginatedDataList.jsx b/awx/ui_next/src/components/PaginatedDataList/PaginatedDataList.jsx index aaeed5455b..6a30d821b0 100644 --- a/awx/ui_next/src/components/PaginatedDataList/PaginatedDataList.jsx +++ b/awx/ui_next/src/components/PaginatedDataList/PaginatedDataList.jsx @@ -13,11 +13,7 @@ import ContentLoading from '../ContentLoading'; import Pagination from '../Pagination'; import DataListToolbar from '../DataListToolbar'; -import { - encodeNonDefaultQueryString, - parseQueryString, - replaceParams, -} from '../../util/qs'; +import { parseQueryString, replaceNamespacedParams } from '../../util/qs'; import { QSConfig, SearchColumns, SortColumns } from '../../types'; @@ -40,7 +36,6 @@ function PaginatedDataList({ pluralizedItemName, showPageSizeOptions, location, - renderToolbar, }) { const { search, pathname } = useLocation(); @@ -51,22 +46,21 @@ function PaginatedDataList({ }; const handleSetPage = (event, pageNumber) => { - const oldParams = parseQueryString(qsConfig, search); - pushHistoryState(replaceParams(oldParams, { page: pageNumber })); + const encodedParams = replaceNamespacedParams(qsConfig, search, { + page: pageNumber, + }); + pushHistoryState(encodedParams); }; const handleSetPageSize = (event, pageSize, page) => { - const oldParams = parseQueryString(qsConfig, search); - pushHistoryState(replaceParams(oldParams, { page_size: pageSize, page })); + const encodedParams = replaceNamespacedParams(qsConfig, search, { + page_size: pageSize, + page, + }); + pushHistoryState(encodedParams); }; - const pushHistoryState = params => { - const nonNamespacedParams = parseQueryString({}, history.location.search); - const encodedParams = encodeNonDefaultQueryString( - qsConfig, - params, - nonNamespacedParams - ); + const pushHistoryState = encodedParams => { history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname); }; diff --git a/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx b/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx index a7b076da57..368c3eac23 100644 --- a/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx +++ b/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx @@ -3,11 +3,7 @@ import React from 'react'; import { useLocation, useHistory } from 'react-router-dom'; import { Thead, Tr, Th as PFTh } from '@patternfly/react-table'; import styled from 'styled-components'; -import { - encodeNonDefaultQueryString, - parseQueryString, - replaceParams, -} from '../../util/qs'; +import { parseQueryString, replaceNamespacedParams } from '../../util/qs'; const Th = styled(PFTh)` --pf-c-table--cell--Overflow: initial; @@ -25,16 +21,10 @@ export default function HeaderRow({ const params = parseQueryString(qsConfig, location.search); const onSort = (key, order) => { - const newParams = replaceParams(params, { + const encodedParams = replaceNamespacedParams(qsConfig, location.search, { order_by: order === 'asc' ? key : `-${key}`, page: null, }); - const nonNamespacedParams = parseQueryString({}, history.location.search); - const encodedParams = encodeNonDefaultQueryString( - qsConfig, - newParams, - nonNamespacedParams - ); history.push( encodedParams ? `${location.pathname}?${encodedParams}` diff --git a/awx/ui_next/src/components/PaginatedTable/PaginatedTable.jsx b/awx/ui_next/src/components/PaginatedTable/PaginatedTable.jsx index 5bced479a6..d73082162a 100644 --- a/awx/ui_next/src/components/PaginatedTable/PaginatedTable.jsx +++ b/awx/ui_next/src/components/PaginatedTable/PaginatedTable.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { TableComposable, Tbody } from '@patternfly/react-table'; import { t } from '@lingui/macro'; -import { useHistory } from 'react-router-dom'; +import { useLocation, useHistory } from 'react-router-dom'; import ListHeader from '../ListHeader'; import ContentEmpty from '../ContentEmpty'; @@ -14,11 +14,7 @@ import Pagination from '../Pagination'; import DataListToolbar from '../DataListToolbar'; import LoadingSpinner from '../LoadingSpinner'; -import { - encodeNonDefaultQueryString, - parseQueryString, - replaceParams, -} from '../../util/qs'; +import { parseQueryString, replaceNamespacedParams } from '../../util/qs'; import { QSConfig, SearchColumns } from '../../types'; function PaginatedTable({ @@ -35,32 +31,30 @@ function PaginatedTable({ toolbarRelatedSearchableKeys, pluralizedItemName, showPageSizeOptions, - renderToolbar, emptyContentMessage, ouiaId, }) { + const { search, pathname } = useLocation(); const history = useHistory(); - const pushHistoryState = params => { - const { pathname, search } = history.location; - const nonNamespacedParams = parseQueryString({}, search); - const encodedParams = encodeNonDefaultQueryString( - qsConfig, - params, - nonNamespacedParams - ); + const pushHistoryState = encodedParams => { history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname); }; const handleSetPage = (event, pageNumber) => { - const oldParams = parseQueryString(qsConfig, history.location.search); - pushHistoryState(replaceParams(oldParams, { page: pageNumber })); + const encodedParams = replaceNamespacedParams(qsConfig, search, { + page: pageNumber, + }); + pushHistoryState(encodedParams); }; const handleSetPageSize = (event, pageSize, page) => { - const oldParams = parseQueryString(qsConfig, history.location.search); - pushHistoryState(replaceParams(oldParams, { page_size: pageSize, page })); + const encodedParams = replaceNamespacedParams(qsConfig, search, { + page_size: pageSize, + page, + }); + pushHistoryState(encodedParams); }; const searchColumns = toolbarSearchColumns.length diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/useWsInventories.js b/awx/ui_next/src/screens/Inventory/InventoryList/useWsInventories.js index 7797256064..c975100906 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryList/useWsInventories.js +++ b/awx/ui_next/src/screens/Inventory/InventoryList/useWsInventories.js @@ -1,10 +1,6 @@ import { useState, useEffect } from 'react'; import { useLocation, useHistory } from 'react-router-dom'; -import { - parseQueryString, - replaceParams, - encodeNonDefaultQueryString, -} from '../../../util/qs'; +import { parseQueryString, replaceNamespacedParams } from '../../../util/qs'; import useWebsocket from '../../../util/useWebsocket'; import useThrottle from '../../../util/useThrottle'; @@ -90,12 +86,9 @@ export default function useWsInventories( ) { // We've deleted the last inventory on this page so we'll // try to navigate back to the previous page - const newParams = encodeNonDefaultQueryString( - qsConfig, - replaceParams(params, { - page: params.page - 1, - }) - ); + const newParams = replaceNamespacedParams(qsConfig, location.search, { + page: params.page - 1, + }); history.push(`${location.pathname}?${newParams}`); return; } diff --git a/awx/ui_next/src/util/qs.js b/awx/ui_next/src/util/qs.js index 729a28790d..75981ce192 100644 --- a/awx/ui_next/src/util/qs.js +++ b/awx/ui_next/src/util/qs.js @@ -246,3 +246,22 @@ export function replaceParams(oldParams, newParams) { ...newParams, }; } + +/** + * Update namespaced param(s), returning a new query string. Leaves params + * from other namespaces unaltered + * @param {object} qs config object for namespacing params, filtering defaults + * @param {string} the url query string to update + * @param {object} namespaced params to add or update + * @return {string} url query string + */ +export function replaceNamespacedParams(config, queryString, newParams) { + const oldParams = parseQueryString(config, queryString); + const updatedParams = replaceParams(oldParams, newParams); + const nonNamespacedParams = parseQueryString({}, queryString); + return encodeNonDefaultQueryString( + config, + updatedParams, + nonNamespacedParams + ); +} diff --git a/awx/ui_next/src/util/qs.test.js b/awx/ui_next/src/util/qs.test.js index 763b07434c..f3886ab7eb 100644 --- a/awx/ui_next/src/util/qs.test.js +++ b/awx/ui_next/src/util/qs.test.js @@ -8,6 +8,7 @@ import { _addDefaultsToObject, mergeParams, replaceParams, + replaceNamespacedParams, } from './qs'; describe('qs (qs.js)', () => { @@ -810,4 +811,64 @@ describe('qs (qs.js)', () => { }); }); }); + + describe('replaceNamespacedParams', () => { + const config = { + namespace: 'template', + defaultParams: { page: 1, page_size: 5, order_by: 'name' }, + integerFields: ['page'], + }; + + test('should update namespaced param', () => { + const query = 'template.name__icontains=workflow&template.page=2'; + const newParams = { + page: 3, + }; + expect(replaceNamespacedParams(config, query, newParams)).toEqual( + 'template.name__icontains=workflow&template.page=3' + ); + }); + + test('should add new namespaced param', () => { + const query = 'template.name__icontains=workflow&template.page=2'; + const newParams = { + or__type: 'job_template', + }; + expect(replaceNamespacedParams(config, query, newParams)).toEqual( + 'template.name__icontains=workflow&template.or__type=job_template&template.page=2' + ); + }); + + test('should maintain non-namespaced param', () => { + const query = 'foo=bar&template.page=2&template.name__icontains=workflow'; + const newParams = { + page: 3, + }; + expect(replaceNamespacedParams(config, query, newParams)).toEqual( + 'foo=bar&template.name__icontains=workflow&template.page=3' + ); + }); + + test('should omit null values', () => { + const query = 'template.name__icontains=workflow&template.page=2'; + const newParams = { + page: 3, + name__icontains: null, + }; + expect(replaceNamespacedParams(config, query, newParams)).toEqual( + 'template.page=3' + ); + }); + + test.skip('should not alter params of other namespaces', () => { + const query = + 'template.name__icontains=workflow&template.page=2&credential.page=3'; + const newParams = { + page: 3, + }; + expect(replaceNamespacedParams(config, query, newParams)).toEqual( + 'template.name__icontains=workflow&template.page=3&credential.page=3' + ); + }); + }); }); diff --git a/awx/ui_next/src/util/useRequest.js b/awx/ui_next/src/util/useRequest.js index c3328ce7c7..50be3fc49a 100644 --- a/awx/ui_next/src/util/useRequest.js +++ b/awx/ui_next/src/util/useRequest.js @@ -4,6 +4,7 @@ import { parseQueryString, replaceParams, encodeNonDefaultQueryString, + replaceNamespacedParams, } from './qs'; import useIsMounted from './useIsMounted'; @@ -111,12 +112,9 @@ export function useDeleteItems( } const params = parseQueryString(qsConfig, location.search); if (params.page > 1 && allItemsSelected) { - const newParams = encodeNonDefaultQueryString( - qsConfig, - replaceParams(params, { - page: params.page - 1, - }) - ); + const newParams = replaceNamespacedParams(qsConfig, location.search, { + page: params.page - 1, + }); history.push(`${location.pathname}?${newParams}`); } else { fetchItems();