mirror of
https://github.com/ansible/awx.git
synced 2026-02-16 18:50:04 -03:30
Merge pull request #10215 from keithjgrant/9218-sort-in-modal
fix namespaced url params SUMMARY Fixes issues when multiple lists are on the page (generally only occurs now when on a list page with an open modal that contains a second list) — Navigating within the modal list currently wipes out URL parameters for the main list, causing the page to reload, which closes the modal. The fix prevents changes to one set of namespaced URL parameters from wiping out URL parameters from another namespace Refactors query string utils to consolidate a lot of repeated logic from components into a new util, updateQueryString. Use this function to modify query string parameters while maintaining params for multiple namespaces at the same time. QS utils that are no longer needed have been deleted: replaceParams and encodeNonDefaultQueryString Addresses #10181 and #9218 ISSUE TYPE Bugfix Pull Request COMPONENT NAME UI Reviewed-by: Keith Grant <keithjgrant@gmail.com> Reviewed-by: John Mitchell <None> Reviewed-by: Alex Corey <Alex.swansboro@gmail.com>
This commit is contained in:
@@ -6,11 +6,10 @@ import { Toolbar, ToolbarContent } from '@patternfly/react-core';
|
|||||||
import DataListToolbar from '../DataListToolbar';
|
import DataListToolbar from '../DataListToolbar';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
mergeParams,
|
mergeParams,
|
||||||
replaceParams,
|
|
||||||
removeParams,
|
removeParams,
|
||||||
|
updateQueryString,
|
||||||
} from '../../util/qs';
|
} from '../../util/qs';
|
||||||
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
||||||
|
|
||||||
@@ -38,60 +37,57 @@ class ListHeader extends React.Component {
|
|||||||
|
|
||||||
handleSearch(key, value) {
|
handleSearch(key, value) {
|
||||||
const { location, qsConfig } = this.props;
|
const { location, qsConfig } = this.props;
|
||||||
let params = parseQueryString(qsConfig, location.search);
|
const params = parseQueryString(qsConfig, location.search);
|
||||||
params = mergeParams(params, { [key]: value });
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
params = replaceParams(params, { page: 1 });
|
...mergeParams(params, { [key]: value }),
|
||||||
this.pushHistoryState(params);
|
page: 1,
|
||||||
|
});
|
||||||
|
this.pushHistoryState(qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReplaceSearch(key, value) {
|
handleReplaceSearch(key, value) {
|
||||||
const { location, qsConfig } = this.props;
|
const { location, qsConfig } = this.props;
|
||||||
const oldParams = parseQueryString(qsConfig, location.search);
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
this.pushHistoryState(replaceParams(oldParams, { [key]: value }));
|
[key]: value,
|
||||||
|
});
|
||||||
|
this.pushHistoryState(qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove(key, value) {
|
handleRemove(key, value) {
|
||||||
const { location, qsConfig } = this.props;
|
const { location, qsConfig } = this.props;
|
||||||
let oldParams = parseQueryString(qsConfig, location.search);
|
const oldParams = parseQueryString(qsConfig, location.search);
|
||||||
if (parseInt(value, 10)) {
|
const updatedParams = removeParams(qsConfig, oldParams, {
|
||||||
oldParams = removeParams(qsConfig, oldParams, {
|
[key]: value,
|
||||||
[key]: parseInt(value, 10),
|
});
|
||||||
});
|
const qs = updateQueryString(qsConfig, location.search, updatedParams);
|
||||||
}
|
this.pushHistoryState(qs);
|
||||||
this.pushHistoryState(removeParams(qsConfig, oldParams, { [key]: value }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemoveAll() {
|
handleRemoveAll() {
|
||||||
// remove everything in oldParams except for page_size and order_by
|
|
||||||
const { location, qsConfig } = this.props;
|
const { location, qsConfig } = this.props;
|
||||||
const oldParams = parseQueryString(qsConfig, location.search);
|
const oldParams = parseQueryString(qsConfig, location.search);
|
||||||
const oldParamsClone = { ...oldParams };
|
Object.keys(oldParams).forEach(key => {
|
||||||
delete oldParamsClone.page_size;
|
oldParams[key] = null;
|
||||||
delete oldParamsClone.order_by;
|
});
|
||||||
this.pushHistoryState(removeParams(qsConfig, oldParams, oldParamsClone));
|
delete oldParams.page_size;
|
||||||
|
delete oldParams.order_by;
|
||||||
|
const qs = updateQueryString(qsConfig, location.search, oldParams);
|
||||||
|
this.pushHistoryState(qs);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSort(key, order) {
|
handleSort(key, order) {
|
||||||
const { location, qsConfig } = this.props;
|
const { location, qsConfig } = this.props;
|
||||||
const oldParams = parseQueryString(qsConfig, location.search);
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
this.pushHistoryState(
|
order_by: order === 'ascending' ? key : `-${key}`,
|
||||||
replaceParams(oldParams, {
|
page: null,
|
||||||
order_by: order === 'ascending' ? key : `-${key}`,
|
});
|
||||||
page: null,
|
this.pushHistoryState(qs);
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pushHistoryState(params) {
|
pushHistoryState(queryString) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history } = this.props;
|
||||||
const { pathname } = history.location;
|
const { pathname } = history.location;
|
||||||
const nonNamespacedParams = parseQueryString({}, history.location.search);
|
history.push(queryString ? `${pathname}?${queryString}` : pathname);
|
||||||
const encodedParams = encodeNonDefaultQueryString(
|
|
||||||
qsConfig,
|
|
||||||
params,
|
|
||||||
nonNamespacedParams
|
|
||||||
);
|
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ describe('ListHeader', () => {
|
|||||||
expect(history.location.search).toEqual('');
|
expect(history.location.search).toEqual('');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should test clear all', () => {
|
test('should clear all', () => {
|
||||||
const query = '?item.page_size=5&item.name=foo';
|
const query = '?item.page_size=5&item.name=foo';
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: [`/organizations/1/teams${query}`],
|
initialEntries: [`/organizations/1/teams${query}`],
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ import ContentLoading from '../ContentLoading';
|
|||||||
import Pagination from '../Pagination';
|
import Pagination from '../Pagination';
|
||||||
import DataListToolbar from '../DataListToolbar';
|
import DataListToolbar from '../DataListToolbar';
|
||||||
|
|
||||||
import {
|
import { parseQueryString, updateQueryString } from '../../util/qs';
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
|
||||||
replaceParams,
|
|
||||||
} from '../../util/qs';
|
|
||||||
|
|
||||||
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
import { QSConfig, SearchColumns, SortColumns } from '../../types';
|
||||||
|
|
||||||
@@ -40,7 +36,6 @@ function PaginatedDataList({
|
|||||||
pluralizedItemName,
|
pluralizedItemName,
|
||||||
showPageSizeOptions,
|
showPageSizeOptions,
|
||||||
location,
|
location,
|
||||||
|
|
||||||
renderToolbar,
|
renderToolbar,
|
||||||
}) {
|
}) {
|
||||||
const { search, pathname } = useLocation();
|
const { search, pathname } = useLocation();
|
||||||
@@ -51,23 +46,22 @@ function PaginatedDataList({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSetPage = (event, pageNumber) => {
|
const handleSetPage = (event, pageNumber) => {
|
||||||
const oldParams = parseQueryString(qsConfig, search);
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
pushHistoryState(replaceParams(oldParams, { page: pageNumber }));
|
page: pageNumber,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetPageSize = (event, pageSize, page) => {
|
const handleSetPageSize = (event, pageSize, page) => {
|
||||||
const oldParams = parseQueryString(qsConfig, search);
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
pushHistoryState(replaceParams(oldParams, { page_size: pageSize, page }));
|
page_size: pageSize,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushHistoryState = params => {
|
const pushHistoryState = qs => {
|
||||||
const nonNamespacedParams = parseQueryString({}, history.location.search);
|
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||||
const encodedParams = encodeNonDefaultQueryString(
|
|
||||||
qsConfig,
|
|
||||||
params,
|
|
||||||
nonNamespacedParams
|
|
||||||
);
|
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchColumns = toolbarSearchColumns.length
|
const searchColumns = toolbarSearchColumns.length
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ import React from 'react';
|
|||||||
import { useLocation, useHistory } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
import { Thead, Tr, Th as PFTh } from '@patternfly/react-table';
|
import { Thead, Tr, Th as PFTh } from '@patternfly/react-table';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import { parseQueryString, updateQueryString } from '../../util/qs';
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
|
||||||
replaceParams,
|
|
||||||
} from '../../util/qs';
|
|
||||||
|
|
||||||
const Th = styled(PFTh)`
|
const Th = styled(PFTh)`
|
||||||
--pf-c-table--cell--Overflow: initial;
|
--pf-c-table--cell--Overflow: initial;
|
||||||
@@ -25,21 +21,11 @@ export default function HeaderRow({
|
|||||||
const params = parseQueryString(qsConfig, location.search);
|
const params = parseQueryString(qsConfig, location.search);
|
||||||
|
|
||||||
const onSort = (key, order) => {
|
const onSort = (key, order) => {
|
||||||
const newParams = replaceParams(params, {
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
order_by: order === 'asc' ? key : `-${key}`,
|
order_by: order === 'asc' ? key : `-${key}`,
|
||||||
page: null,
|
page: null,
|
||||||
});
|
});
|
||||||
const nonNamespacedParams = parseQueryString({}, history.location.search);
|
history.push(qs ? `${location.pathname}?${qs}` : location.pathname);
|
||||||
const encodedParams = encodeNonDefaultQueryString(
|
|
||||||
qsConfig,
|
|
||||||
newParams,
|
|
||||||
nonNamespacedParams
|
|
||||||
);
|
|
||||||
history.push(
|
|
||||||
encodedParams
|
|
||||||
? `${location.pathname}?${encodedParams}`
|
|
||||||
: location.pathname
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortKey = params.order_by?.replace('-', '');
|
const sortKey = params.order_by?.replace('-', '');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { TableComposable, Tbody } from '@patternfly/react-table';
|
import { TableComposable, Tbody } from '@patternfly/react-table';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import ListHeader from '../ListHeader';
|
import ListHeader from '../ListHeader';
|
||||||
import ContentEmpty from '../ContentEmpty';
|
import ContentEmpty from '../ContentEmpty';
|
||||||
@@ -14,11 +14,7 @@ import Pagination from '../Pagination';
|
|||||||
import DataListToolbar from '../DataListToolbar';
|
import DataListToolbar from '../DataListToolbar';
|
||||||
import LoadingSpinner from '../LoadingSpinner';
|
import LoadingSpinner from '../LoadingSpinner';
|
||||||
|
|
||||||
import {
|
import { parseQueryString, updateQueryString } from '../../util/qs';
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
|
||||||
replaceParams,
|
|
||||||
} from '../../util/qs';
|
|
||||||
import { QSConfig, SearchColumns } from '../../types';
|
import { QSConfig, SearchColumns } from '../../types';
|
||||||
|
|
||||||
function PaginatedTable({
|
function PaginatedTable({
|
||||||
@@ -39,27 +35,26 @@ function PaginatedTable({
|
|||||||
emptyContentMessage,
|
emptyContentMessage,
|
||||||
ouiaId,
|
ouiaId,
|
||||||
}) {
|
}) {
|
||||||
|
const { search, pathname } = useLocation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const pushHistoryState = params => {
|
const pushHistoryState = qs => {
|
||||||
const { pathname, search } = history.location;
|
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||||
const nonNamespacedParams = parseQueryString({}, search);
|
|
||||||
const encodedParams = encodeNonDefaultQueryString(
|
|
||||||
qsConfig,
|
|
||||||
params,
|
|
||||||
nonNamespacedParams
|
|
||||||
);
|
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetPage = (event, pageNumber) => {
|
const handleSetPage = (event, pageNumber) => {
|
||||||
const oldParams = parseQueryString(qsConfig, history.location.search);
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
pushHistoryState(replaceParams(oldParams, { page: pageNumber }));
|
page: pageNumber,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetPageSize = (event, pageSize, page) => {
|
const handleSetPageSize = (event, pageSize, page) => {
|
||||||
const oldParams = parseQueryString(qsConfig, history.location.search);
|
const qs = updateQueryString(qsConfig, search, {
|
||||||
pushHistoryState(replaceParams(oldParams, { page_size: pageSize, page }));
|
page_size: pageSize,
|
||||||
|
page,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchColumns = toolbarSearchColumns.length
|
const searchColumns = toolbarSearchColumns.length
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ import useRequest from '../../util/useRequest';
|
|||||||
import {
|
import {
|
||||||
getQSConfig,
|
getQSConfig,
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
replaceParams,
|
updateQueryString,
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
} from '../../util/qs';
|
} from '../../util/qs';
|
||||||
import { ActivityStreamAPI } from '../../api';
|
import { ActivityStreamAPI } from '../../api';
|
||||||
|
|
||||||
@@ -96,16 +95,14 @@ function ActivityStream() {
|
|||||||
}, [fetchActivityStream]);
|
}, [fetchActivityStream]);
|
||||||
|
|
||||||
const pushHistoryState = urlParamsToAdd => {
|
const pushHistoryState = urlParamsToAdd => {
|
||||||
let searchParams = parseQueryString(QS_CONFIG, location.search);
|
const pageOneQs = updateQueryString(QS_CONFIG, location.search, {
|
||||||
searchParams = replaceParams(searchParams, { page: 1 });
|
page: 1,
|
||||||
const encodedParams = encodeNonDefaultQueryString(QS_CONFIG, searchParams, {
|
});
|
||||||
|
const qs = updateQueryString(null, pageOneQs, {
|
||||||
type: urlParamsToAdd.get('type'),
|
type: urlParamsToAdd.get('type'),
|
||||||
});
|
});
|
||||||
history.push(
|
|
||||||
encodedParams
|
history.push(qs ? `${location.pathname}?${qs}` : location.pathname);
|
||||||
? `${location.pathname}?${encodedParams}`
|
|
||||||
: location.pathname
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function Host({ setBreadcrumb }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchHost();
|
fetchHost();
|
||||||
}, [fetchHost, location]);
|
}, [fetchHost, location.pathname]);
|
||||||
|
|
||||||
const tabsArray = [
|
const tabsArray = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useLocation, useHistory } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
import {
|
import { parseQueryString, updateQueryString } from '../../../util/qs';
|
||||||
parseQueryString,
|
|
||||||
replaceParams,
|
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
} from '../../../util/qs';
|
|
||||||
import useWebsocket from '../../../util/useWebsocket';
|
import useWebsocket from '../../../util/useWebsocket';
|
||||||
import useThrottle from '../../../util/useThrottle';
|
import useThrottle from '../../../util/useThrottle';
|
||||||
|
|
||||||
@@ -90,13 +86,10 @@ export default function useWsInventories(
|
|||||||
) {
|
) {
|
||||||
// We've deleted the last inventory on this page so we'll
|
// We've deleted the last inventory on this page so we'll
|
||||||
// try to navigate back to the previous page
|
// try to navigate back to the previous page
|
||||||
const newParams = encodeNonDefaultQueryString(
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
qsConfig,
|
page: params.page - 1,
|
||||||
replaceParams(params, {
|
});
|
||||||
page: params.page - 1,
|
history.push(`${location.pathname}?${qs}`);
|
||||||
})
|
|
||||||
);
|
|
||||||
history.push(`${location.pathname}?${newParams}`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ import getRowRangePageSize from './shared/jobOutputUtils';
|
|||||||
import { getJobModel, isJobRunning } from '../../../util/jobs';
|
import { getJobModel, isJobRunning } from '../../../util/jobs';
|
||||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||||
import {
|
import {
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
mergeParams,
|
mergeParams,
|
||||||
replaceParams,
|
|
||||||
removeParams,
|
removeParams,
|
||||||
getQSConfig,
|
getQSConfig,
|
||||||
|
updateQueryString,
|
||||||
} from '../../../util/qs';
|
} from '../../../util/qs';
|
||||||
import useIsMounted from '../../../util/useIsMounted';
|
import useIsMounted from '../../../util/useIsMounted';
|
||||||
|
|
||||||
@@ -589,35 +588,43 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (key, value) => {
|
const handleSearch = (key, value) => {
|
||||||
let params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
params = mergeParams(params, { [key]: value });
|
const qs = updateQueryString(
|
||||||
pushHistoryState(params);
|
QS_CONFIG,
|
||||||
|
location.search,
|
||||||
|
mergeParams(params, { [key]: value })
|
||||||
|
);
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReplaceSearch = (key, value) => {
|
const handleReplaceSearch = (key, value) => {
|
||||||
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
const qs = updateQueryString(QS_CONFIG, location.search, {
|
||||||
pushHistoryState(replaceParams(oldParams, { [key]: value }));
|
[key]: value,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveSearchTerm = (key, value) => {
|
const handleRemoveSearchTerm = (key, value) => {
|
||||||
let oldParams = parseQueryString(QS_CONFIG, location.search);
|
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
||||||
if (parseInt(value, 10)) {
|
const updatedParams = removeParams(QS_CONFIG, oldParams, {
|
||||||
oldParams = removeParams(QS_CONFIG, oldParams, {
|
[key]: value,
|
||||||
[key]: parseInt(value, 10),
|
});
|
||||||
});
|
const qs = updateQueryString(QS_CONFIG, location.search, updatedParams);
|
||||||
}
|
pushHistoryState(qs);
|
||||||
pushHistoryState(removeParams(QS_CONFIG, oldParams, { [key]: value }));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveAllSearchTerms = () => {
|
const handleRemoveAllSearchTerms = () => {
|
||||||
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
||||||
pushHistoryState(removeParams(QS_CONFIG, oldParams, { ...oldParams }));
|
Object.keys(oldParams).forEach(key => {
|
||||||
|
oldParams[key] = null;
|
||||||
|
});
|
||||||
|
const qs = updateQueryString(QS_CONFIG, location.search, oldParams);
|
||||||
|
pushHistoryState(qs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pushHistoryState = params => {
|
const pushHistoryState = qs => {
|
||||||
const { pathname } = history.location;
|
const { pathname } = history.location;
|
||||||
const encodedParams = encodeNonDefaultQueryString(QS_CONFIG, params);
|
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSearchComponent = () => (
|
const renderSearchComponent = () => (
|
||||||
|
|||||||
@@ -113,44 +113,6 @@ function encodeValue(key, value) {
|
|||||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert query param object to url query string, adding namespace and
|
|
||||||
* removing defaults. Used to put into url bar after ui route
|
|
||||||
* @param {object} qs config object for namespacing params, filtering defaults
|
|
||||||
* @param {object} query param object
|
|
||||||
* @param {object} any non-namespaced params to append
|
|
||||||
* @return {string} url query string
|
|
||||||
*/
|
|
||||||
export const encodeNonDefaultQueryString = (
|
|
||||||
config,
|
|
||||||
params,
|
|
||||||
nonNamespacedParams = {}
|
|
||||||
) => {
|
|
||||||
if (!params) return '';
|
|
||||||
const paramsWithoutDefaults = removeParams({}, params, config.defaultParams);
|
|
||||||
return encodeQueryString({
|
|
||||||
...namespaceParams(config.namespace, paramsWithoutDefaults),
|
|
||||||
...nonNamespacedParams,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to namespace params object
|
|
||||||
* @param {string} namespace to append to params
|
|
||||||
* @param {object} params object to append namespace to
|
|
||||||
* @return {object} params object with namespaced keys
|
|
||||||
*/
|
|
||||||
const namespaceParams = (namespace, params) => {
|
|
||||||
if (!namespace) return params;
|
|
||||||
|
|
||||||
const namespaced = {};
|
|
||||||
Object.keys(params).forEach(key => {
|
|
||||||
namespaced[`${namespace}.${key}`] = params[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
return namespaced;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes params from the search string and returns the updated list of params
|
* Removes params from the search string and returns the updated list of params
|
||||||
* @param {object} qs config object (used for getting defaults, current query params etc.)
|
* @param {object} qs config object (used for getting defaults, current query params etc.)
|
||||||
@@ -163,10 +125,19 @@ export function removeParams(config, oldParams, paramsToRemove) {
|
|||||||
...config.defaultParams,
|
...config.defaultParams,
|
||||||
};
|
};
|
||||||
Object.keys(oldParams).forEach(key => {
|
Object.keys(oldParams).forEach(key => {
|
||||||
const value = removeParam(oldParams[key], paramsToRemove[key]);
|
const valToRemove = paramsToRemove[key];
|
||||||
if (value !== null) {
|
const isInt = config.integerFields?.includes(key);
|
||||||
updated[key] = value;
|
const updatedValue = removeParam(
|
||||||
|
oldParams[key],
|
||||||
|
isInt ? parseInt(valToRemove, 10) : valToRemove
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
updatedValue == null &&
|
||||||
|
Object.prototype.hasOwnProperty.call(updated, key)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
updated[key] = updatedValue;
|
||||||
});
|
});
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
@@ -234,15 +205,42 @@ function dedupeArray(arr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join old and new params together, replacing old values with new ones where
|
* Update namespaced param(s), returning a new query string. Leaves params
|
||||||
* necessary
|
* from other namespaces unaltered
|
||||||
* @param {object} namespaced params object of old params
|
* @param {object} qs config object for namespacing params, filtering defaults
|
||||||
* @param {object} namespaced params object of new params
|
* @param {string} the url query string to update
|
||||||
* @return {object} joined namespaced params object
|
* @param {object} namespaced params to add or update. use null to indicate
|
||||||
|
* a param that should be deleted from the query string
|
||||||
|
* @return {string} url query string
|
||||||
*/
|
*/
|
||||||
export function replaceParams(oldParams, newParams) {
|
export function updateQueryString(config, queryString, newParams) {
|
||||||
return {
|
const allParams = parseFullQueryString(queryString);
|
||||||
...oldParams,
|
const { namespace = null, defaultParams = {} } = config || {};
|
||||||
...newParams,
|
Object.keys(newParams).forEach(key => {
|
||||||
};
|
const val = newParams[key];
|
||||||
|
const fullKey = namespace ? `${namespace}.${key}` : key;
|
||||||
|
if (val === null || val === defaultParams[key]) {
|
||||||
|
delete allParams[fullKey];
|
||||||
|
} else {
|
||||||
|
allParams[fullKey] = newParams[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return encodeQueryString(allParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFullQueryString(queryString) {
|
||||||
|
const allParams = {};
|
||||||
|
queryString
|
||||||
|
.replace(/^\?/, '')
|
||||||
|
.split('&')
|
||||||
|
.map(s => s.split('='))
|
||||||
|
.forEach(([rawKey, rawValue]) => {
|
||||||
|
if (!rawKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = decodeURIComponent(rawKey);
|
||||||
|
const value = decodeURIComponent(rawValue);
|
||||||
|
allParams[key] = mergeParam(allParams[key], value);
|
||||||
|
});
|
||||||
|
return allParams;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
encodeQueryString,
|
encodeQueryString,
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
getQSConfig,
|
getQSConfig,
|
||||||
removeParams,
|
removeParams,
|
||||||
_stringToObject,
|
_stringToObject,
|
||||||
_addDefaultsToObject,
|
_addDefaultsToObject,
|
||||||
mergeParams,
|
mergeParams,
|
||||||
replaceParams,
|
updateQueryString,
|
||||||
} from './qs';
|
} from './qs';
|
||||||
|
|
||||||
describe('qs (qs.js)', () => {
|
describe('qs (qs.js)', () => {
|
||||||
@@ -47,70 +46,6 @@ describe('qs (qs.js)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('encodeNonDefaultQueryString', () => {
|
|
||||||
const config = {
|
|
||||||
namespace: null,
|
|
||||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
|
||||||
integerFields: ['page'],
|
|
||||||
};
|
|
||||||
|
|
||||||
test('should return the expected queryString', () => {
|
|
||||||
[
|
|
||||||
[null, ''],
|
|
||||||
[{}, ''],
|
|
||||||
[{ order_by: 'name', page: 1, page_size: 5 }, ''],
|
|
||||||
[{ order_by: '-name', page: 1, page_size: 5 }, 'order_by=-name'],
|
|
||||||
[
|
|
||||||
{ order_by: '-name', page: 3, page_size: 10 },
|
|
||||||
'order_by=-name&page=3&page_size=10',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{ order_by: '-name', page: 3, page_size: 10, foo: 'bar' },
|
|
||||||
'foo=bar&order_by=-name&page=3&page_size=10',
|
|
||||||
],
|
|
||||||
].forEach(([params, expectedQueryString]) => {
|
|
||||||
const actualQueryString = encodeNonDefaultQueryString(config, params);
|
|
||||||
|
|
||||||
expect(actualQueryString).toEqual(expectedQueryString);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should omit null values', () => {
|
|
||||||
const vals = {
|
|
||||||
order_by: 'foo',
|
|
||||||
page: null,
|
|
||||||
};
|
|
||||||
expect(encodeNonDefaultQueryString(config, vals)).toEqual('order_by=foo');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should namespace encoded params', () => {
|
|
||||||
const conf = {
|
|
||||||
namespace: 'item',
|
|
||||||
defaultParams: { page: 1 },
|
|
||||||
};
|
|
||||||
const params = {
|
|
||||||
page: 1,
|
|
||||||
foo: 'bar',
|
|
||||||
};
|
|
||||||
expect(encodeNonDefaultQueryString(conf, params)).toEqual('item.foo=bar');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should handle array values', () => {
|
|
||||||
const vals = {
|
|
||||||
foo: ['one', 'two'],
|
|
||||||
bar: ['alpha', 'beta'],
|
|
||||||
};
|
|
||||||
const conf = {
|
|
||||||
defaultParams: {
|
|
||||||
foo: ['one', 'two'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
expect(encodeNonDefaultQueryString(conf, vals)).toEqual(
|
|
||||||
'bar=alpha&bar=beta'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getQSConfig', () => {
|
describe('getQSConfig', () => {
|
||||||
test('should get default QS config object', () => {
|
test('should get default QS config object', () => {
|
||||||
expect(getQSConfig('organization')).toEqual({
|
expect(getQSConfig('organization')).toEqual({
|
||||||
@@ -340,6 +275,7 @@ describe('qs (qs.js)', () => {
|
|||||||
baz: 'bar',
|
baz: 'bar',
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
bag: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,6 +364,7 @@ describe('qs (qs.js)', () => {
|
|||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
pat: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,6 +379,7 @@ describe('qs (qs.js)', () => {
|
|||||||
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
baz: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -456,6 +394,7 @@ describe('qs (qs.js)', () => {
|
|||||||
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
baz: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -525,6 +464,7 @@ describe('qs (qs.js)', () => {
|
|||||||
baz: ['one', 'two', 'three'],
|
baz: ['one', 'two', 'three'],
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
bag: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -545,6 +485,7 @@ describe('qs (qs.js)', () => {
|
|||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
|
pat: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -558,10 +499,43 @@ describe('qs (qs.js)', () => {
|
|||||||
const toRemove = { bag: 'boom' };
|
const toRemove = { bag: 'boom' };
|
||||||
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
||||||
baz: '',
|
baz: '',
|
||||||
|
bag: null,
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should remove integer fields when given string value', () => {
|
||||||
|
const config = {
|
||||||
|
namespace: null,
|
||||||
|
defaultParams: { page: 1, page_size: 15 },
|
||||||
|
integerFields: ['id', 'page', 'page_size'],
|
||||||
|
};
|
||||||
|
const oldParams = { id: 199, foo: 'bar', page: 1, page_size: 15 };
|
||||||
|
const toRemove = { id: '199' };
|
||||||
|
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
||||||
|
foo: 'bar',
|
||||||
|
id: null,
|
||||||
|
page: 1,
|
||||||
|
page_size: 15,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove integer fields from array when given string value', () => {
|
||||||
|
const config = {
|
||||||
|
namespace: null,
|
||||||
|
defaultParams: { page: 1, page_size: 15 },
|
||||||
|
integerFields: ['id', 'page', 'page_size'],
|
||||||
|
};
|
||||||
|
const oldParams = { id: [199, 200], foo: 'bar', page: 1, page_size: 15 };
|
||||||
|
const toRemove = { id: '199' };
|
||||||
|
expect(removeParams(config, oldParams, toRemove)).toEqual({
|
||||||
|
foo: 'bar',
|
||||||
|
id: 200,
|
||||||
|
page: 1,
|
||||||
|
page_size: 15,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('_stringToObject', () => {
|
describe('_stringToObject', () => {
|
||||||
@@ -763,51 +737,94 @@ describe('qs (qs.js)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('replaceParams', () => {
|
describe('updateQueryString', () => {
|
||||||
it('should collect params into one object', () => {
|
const config = {
|
||||||
const oldParams = { foo: 'one' };
|
namespace: 'template',
|
||||||
const newParams = { bar: 'two' };
|
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||||
expect(replaceParams(oldParams, newParams)).toEqual({
|
integerFields: ['page'],
|
||||||
foo: 'one',
|
};
|
||||||
bar: 'two',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should retain unaltered params', () => {
|
test('should add param to empty query string', () => {
|
||||||
const oldParams = {
|
|
||||||
foo: 'one',
|
|
||||||
bar: 'baz',
|
|
||||||
};
|
|
||||||
const newParams = { foo: 'two' };
|
|
||||||
expect(replaceParams(oldParams, newParams)).toEqual({
|
|
||||||
foo: 'two',
|
|
||||||
bar: 'baz',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should override old values with new ones', () => {
|
|
||||||
const oldParams = {
|
|
||||||
foo: 'one',
|
|
||||||
bar: 'three',
|
|
||||||
};
|
|
||||||
const newParams = {
|
const newParams = {
|
||||||
foo: 'two',
|
page: 3,
|
||||||
baz: 'four',
|
|
||||||
};
|
};
|
||||||
expect(replaceParams(oldParams, newParams)).toEqual({
|
expect(updateQueryString(config, '', newParams)).toEqual(
|
||||||
foo: 'two',
|
'template.page=3'
|
||||||
bar: 'three',
|
);
|
||||||
baz: 'four',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle exact duplicates', () => {
|
test('should update namespaced param', () => {
|
||||||
const oldParams = { foo: 'one' };
|
const query = 'template.name__icontains=workflow&template.page=2';
|
||||||
const newParams = { foo: 'one', bar: 'two' };
|
const newParams = {
|
||||||
expect(replaceParams(oldParams, newParams)).toEqual({
|
page: 3,
|
||||||
foo: 'one',
|
};
|
||||||
bar: 'two',
|
expect(updateQueryString(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(updateQueryString(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(updateQueryString(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(updateQueryString(config, query, newParams)).toEqual(
|
||||||
|
'template.page=3'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should omit default values', () => {
|
||||||
|
const query = 'template.page=2';
|
||||||
|
const newParams = {
|
||||||
|
page: 3,
|
||||||
|
page_size: 5,
|
||||||
|
};
|
||||||
|
expect(updateQueryString(config, query, newParams)).toEqual(
|
||||||
|
'template.page=3'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update non-namespaced param', () => {
|
||||||
|
const query =
|
||||||
|
'activity_stream.name__icontains=workflow&activity_stream.page=2';
|
||||||
|
const newParams = {
|
||||||
|
type: 'job',
|
||||||
|
};
|
||||||
|
expect(updateQueryString(null, query, newParams)).toEqual(
|
||||||
|
'activity_stream.name__icontains=workflow&activity_stream.page=2&type=job'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not alter params of other namespaces', () => {
|
||||||
|
const query =
|
||||||
|
'template.name__icontains=workflow&template.page=2&credential.page=3';
|
||||||
|
const newParams = {
|
||||||
|
page: 3,
|
||||||
|
};
|
||||||
|
expect(updateQueryString(config, query, newParams)).toEqual(
|
||||||
|
'credential.page=3&template.name__icontains=workflow&template.page=3'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { useLocation, useHistory } from 'react-router-dom';
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
import {
|
import { parseQueryString, updateQueryString } from './qs';
|
||||||
parseQueryString,
|
|
||||||
replaceParams,
|
|
||||||
encodeNonDefaultQueryString,
|
|
||||||
} from './qs';
|
|
||||||
import useIsMounted from './useIsMounted';
|
import useIsMounted from './useIsMounted';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -111,13 +107,10 @@ export function useDeleteItems(
|
|||||||
}
|
}
|
||||||
const params = parseQueryString(qsConfig, location.search);
|
const params = parseQueryString(qsConfig, location.search);
|
||||||
if (params.page > 1 && allItemsSelected) {
|
if (params.page > 1 && allItemsSelected) {
|
||||||
const newParams = encodeNonDefaultQueryString(
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
qsConfig,
|
page: params.page - 1,
|
||||||
replaceParams(params, {
|
});
|
||||||
page: params.page - 1,
|
history.push(`${location.pathname}?${qs}`);
|
||||||
})
|
|
||||||
);
|
|
||||||
history.push(`${location.pathname}?${newParams}`);
|
|
||||||
} else {
|
} else {
|
||||||
fetchItems();
|
fetchItems();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user