mirror of
https://github.com/ansible/awx.git
synced 2026-01-15 20:00:43 -03:30
Add namespacing for query params (#205)
* use qs utils to namespace query params * refactor Lookup and SelectResource Steps to use PaginatedDataList * preserve query params when adding new ones * require namespace for QS Configs
This commit is contained in:
parent
d59975c1ad
commit
4407aeac20
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { mountWithContexts } from '../enzymeHelpers';
|
||||
import Lookup from '../../src/components/Lookup';
|
||||
import { _Lookup } from '../../src/components/Lookup/Lookup';
|
||||
@ -10,8 +11,8 @@ const mockColumns = [
|
||||
describe('<Lookup />', () => {
|
||||
test('initially renders succesfully', () => {
|
||||
mountWithContexts(
|
||||
<_Lookup
|
||||
lookup_header="Foo Bar"
|
||||
<Lookup
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockData}
|
||||
onLookupSave={() => { }}
|
||||
@ -25,8 +26,8 @@ describe('<Lookup />', () => {
|
||||
|
||||
test('API response is formatted properly', (done) => {
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
lookup_header="Foo Bar"
|
||||
<Lookup
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockData}
|
||||
onLookupSave={() => { }}
|
||||
@ -47,9 +48,9 @@ describe('<Lookup />', () => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'handleModalToggle');
|
||||
const mockSelected = [{ name: 'foo', id: 1 }];
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
<Lookup
|
||||
id="search"
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockSelected}
|
||||
onLookupSave={() => { }}
|
||||
@ -74,17 +75,22 @@ describe('<Lookup />', () => {
|
||||
test('calls "toggleSelected" when a user changes a checkbox', (done) => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'toggleSelected');
|
||||
const mockSelected = [{ name: 'foo', id: 1 }];
|
||||
const data = {
|
||||
results: [
|
||||
{ name: 'test instance', id: 1, url: '/foo' }
|
||||
],
|
||||
count: 1,
|
||||
};
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
<Lookup
|
||||
id="search"
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockSelected}
|
||||
onLookupSave={() => { }}
|
||||
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
|
||||
getItems={() => ({ data })}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
);
|
||||
setImmediate(() => {
|
||||
@ -99,17 +105,22 @@ describe('<Lookup />', () => {
|
||||
test('calls "toggleSelected" when remove icon is clicked', () => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'toggleSelected');
|
||||
mockData = [{ name: 'foo', id: 1 }, { name: 'bar', id: 2 }];
|
||||
const data = {
|
||||
results: [
|
||||
{ name: 'test instance', id: 1, url: '/foo' }
|
||||
],
|
||||
count: 1,
|
||||
};
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
<Lookup
|
||||
id="search"
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockData}
|
||||
onLookupSave={() => { }}
|
||||
getItems={() => { }}
|
||||
getItems={() => ({ data })}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
);
|
||||
const removeIcon = wrapper.find('button[aria-label="close"]').first();
|
||||
@ -121,7 +132,7 @@ describe('<Lookup />', () => {
|
||||
mockData = [{ name: 'foo', id: 0 }, { name: 'bar', id: 1 }];
|
||||
const wrapper = mountWithContexts(
|
||||
<Lookup
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
onLookupSave={() => { }}
|
||||
value={mockData}
|
||||
selected={[]}
|
||||
@ -138,7 +149,7 @@ describe('<Lookup />', () => {
|
||||
mockData = [];
|
||||
const wrapper = mountWithContexts(
|
||||
<Lookup
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
onLookupSave={() => { }}
|
||||
value={mockData}
|
||||
getItems={() => { }}
|
||||
@ -166,11 +177,12 @@ describe('<Lookup />', () => {
|
||||
const onLookupSaveFn = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<Lookup
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
name="fooBar"
|
||||
value={mockData}
|
||||
onLookupSave={onLookupSaveFn}
|
||||
getItems={() => { }}
|
||||
sortedColumnKey="name"
|
||||
/>
|
||||
).find('Lookup');
|
||||
wrapper.instance().toggleSelected({
|
||||
@ -188,61 +200,31 @@ describe('<Lookup />', () => {
|
||||
}], 'fooBar');
|
||||
});
|
||||
|
||||
test('onSort sets state and calls getData ', () => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'getData');
|
||||
test('should re-fetch data when URL params change', async () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/add'],
|
||||
});
|
||||
const getItems = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
lookup_header="Foo Bar"
|
||||
lookupHeader="Foo Bar"
|
||||
onLookupSave={() => { }}
|
||||
value={mockData}
|
||||
selected={[]}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
getItems={() => { }}
|
||||
getItems={getItems}
|
||||
handleHttpError={() => {}}
|
||||
location={{ history }}
|
||||
/>
|
||||
).find('Lookup');
|
||||
wrapper.instance().onSort('id', 'descending');
|
||||
expect(wrapper.state('sortedColumnKey')).toEqual('id');
|
||||
expect(wrapper.state('sortOrder')).toEqual('descending');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
);
|
||||
|
||||
test('onSearch calls getData (through calling onSort)', () => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'getData');
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
lookup_header="Foo Bar"
|
||||
onLookupSave={() => { }}
|
||||
value={mockData}
|
||||
selected={[]}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
getItems={() => { }}
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
).find('Lookup');
|
||||
wrapper.instance().onSearch();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('onSetPage sets state and calls getData ', () => {
|
||||
const spy = jest.spyOn(_Lookup.prototype, 'getData');
|
||||
const wrapper = mountWithContexts(
|
||||
<_Lookup
|
||||
lookup_header="Foo Bar"
|
||||
onLookupSave={() => { }}
|
||||
value={mockData}
|
||||
selected={[]}
|
||||
columns={mockColumns}
|
||||
sortedColumnKey="name"
|
||||
getItems={() => { }}
|
||||
handleHttpError={() => {}}
|
||||
/>
|
||||
).find('Lookup');
|
||||
wrapper.instance().onSetPage(2, 10);
|
||||
expect(wrapper.state('page')).toEqual(2);
|
||||
expect(wrapper.state('page_size')).toEqual(10);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(getItems).toHaveBeenCalledTimes(1);
|
||||
history.push('organizations/add?page=2');
|
||||
wrapper.setProps({
|
||||
location: { history },
|
||||
});
|
||||
wrapper.update();
|
||||
expect(getItems).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,6 +12,12 @@ const mockData = [
|
||||
{ id: 5, name: 'five', url: '/org/team/5' },
|
||||
];
|
||||
|
||||
const qsConfig = {
|
||||
namespace: 'item',
|
||||
defaultParams: { page: 1, page_size: 5 },
|
||||
integerFields: [],
|
||||
};
|
||||
|
||||
describe('<PaginatedDataList />', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
@ -27,11 +33,11 @@ describe('<PaginatedDataList />', () => {
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
}}
|
||||
qsConfig={qsConfig}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
// should navigate when datalisttoolbar changes sorting
|
||||
test('should navigate when DataListToolbar calls onSort prop', async () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/1/teams'],
|
||||
@ -45,6 +51,7 @@ describe('<PaginatedDataList />', () => {
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
}}
|
||||
qsConfig={qsConfig}
|
||||
/>, { context: { router: { history } } }
|
||||
);
|
||||
|
||||
@ -52,7 +59,7 @@ describe('<PaginatedDataList />', () => {
|
||||
expect(toolbar.prop('sortedColumnKey')).toEqual('name');
|
||||
expect(toolbar.prop('sortOrder')).toEqual('ascending');
|
||||
toolbar.prop('onSort')('name', 'descending');
|
||||
expect(history.location.search).toEqual('?order_by=-name');
|
||||
expect(history.location.search).toEqual('?item.order_by=-name');
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
@ -61,7 +68,7 @@ describe('<PaginatedDataList />', () => {
|
||||
// fixing after #147 is done:
|
||||
// expect(toolbar.prop('sortOrder')).toEqual('descending');
|
||||
toolbar.prop('onSort')('name', 'ascending');
|
||||
expect(history.location.search).toEqual('?order_by=name');
|
||||
expect(history.location.search).toEqual('?item.order_by=name');
|
||||
});
|
||||
|
||||
test('should navigate to page when Pagination calls onSetPage prop', () => {
|
||||
@ -77,14 +84,15 @@ describe('<PaginatedDataList />', () => {
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
}}
|
||||
qsConfig={qsConfig}
|
||||
/>, { context: { router: { history } } }
|
||||
);
|
||||
|
||||
const pagination = wrapper.find('Pagination');
|
||||
pagination.prop('onSetPage')(2, 5);
|
||||
expect(history.location.search).toEqual('?page=2&page_size=5');
|
||||
expect(history.location.search).toEqual('?item.page=2&item.page_size=5');
|
||||
wrapper.update();
|
||||
pagination.prop('onSetPage')(1, 25);
|
||||
expect(history.location.search).toEqual('?page=1&page_size=25');
|
||||
expect(history.location.search).toEqual('?item.page=1&item.page_size=25');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { shallow } from 'enzyme';
|
||||
import { mountWithContexts } from '../enzymeHelpers';
|
||||
import SelectResourceStep from '../../src/components/AddRole/SelectResourceStep';
|
||||
import { sleep } from '../testUtils';
|
||||
import SelectResourceStep, { _SelectResourceStep } from '../../src/components/AddRole/SelectResourceStep';
|
||||
|
||||
describe('<SelectResourceStep />', () => {
|
||||
const columns = [
|
||||
@ -21,13 +23,14 @@ describe('<SelectResourceStep />', () => {
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
test('fetches resources on mount', async () => {
|
||||
const handleSearch = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
count: 2,
|
||||
results: [
|
||||
{ id: 1, username: 'foo' },
|
||||
{ id: 2, username: 'bar' }
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' }
|
||||
]
|
||||
}
|
||||
});
|
||||
@ -46,102 +49,71 @@ describe('<SelectResourceStep />', () => {
|
||||
page_size: 5
|
||||
});
|
||||
});
|
||||
|
||||
test('readResourceList properly adds rows to state', async () => {
|
||||
const selectedResourceRows = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'foo'
|
||||
}
|
||||
{ id: 1, username: 'foo', url: 'item/1' }
|
||||
];
|
||||
const handleSearch = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
count: 2,
|
||||
results: [
|
||||
{ id: 1, username: 'foo' },
|
||||
{ id: 2, username: 'bar' }
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' }
|
||||
]
|
||||
}
|
||||
});
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/1/access?resource.page=1&resource.order_by=-username'],
|
||||
});
|
||||
const wrapper = await mountWithContexts(
|
||||
<SelectResourceStep
|
||||
<_SelectResourceStep
|
||||
columns={columns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={handleSearch}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
location={history.location}
|
||||
/>
|
||||
).find('SelectResourceStep');
|
||||
await wrapper.instance().readResourceList({
|
||||
page: 1,
|
||||
order_by: '-username'
|
||||
});
|
||||
await wrapper.instance().readResourceList();
|
||||
expect(handleSearch).toHaveBeenCalledWith({
|
||||
order_by: '-username',
|
||||
page: 1
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
});
|
||||
expect(wrapper.state('resources')).toEqual([
|
||||
{ id: 1, username: 'foo' },
|
||||
{ id: 2, username: 'bar' }
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' }
|
||||
]);
|
||||
});
|
||||
test('handleSetPage calls readResourceList with correct params', () => {
|
||||
const spy = jest.spyOn(SelectResourceStep.prototype, 'readResourceList');
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={() => {}}
|
||||
selectedResourceRows={[]}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
).find('SelectResourceStep');
|
||||
wrapper.setState({ sortOrder: 'descending' });
|
||||
wrapper.instance().handleSetPage(2);
|
||||
expect(spy).toHaveBeenCalledWith({ page: 2, page_size: 5, order_by: '-username' });
|
||||
wrapper.setState({ sortOrder: 'ascending' });
|
||||
wrapper.instance().handleSetPage(2);
|
||||
expect(spy).toHaveBeenCalledWith({ page: 2, page_size: 5, order_by: 'username' });
|
||||
});
|
||||
test('handleSort calls readResourceList with correct params', () => {
|
||||
const spy = jest.spyOn(SelectResourceStep.prototype, 'readResourceList');
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
displayKey="username"
|
||||
onRowClick={() => {}}
|
||||
onSearch={() => {}}
|
||||
selectedResourceRows={[]}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
).find('SelectResourceStep');
|
||||
wrapper.instance().handleSort('username', 'descending');
|
||||
expect(spy).toHaveBeenCalledWith({ page: 1, page_size: 5, order_by: '-username' });
|
||||
wrapper.instance().handleSort('username', 'ascending');
|
||||
expect(spy).toHaveBeenCalledWith({ page: 1, page_size: 5, order_by: 'username' });
|
||||
});
|
||||
test('clicking on row fires callback with correct params', () => {
|
||||
|
||||
test('clicking on row fires callback with correct params', async () => {
|
||||
const handleRowClick = jest.fn();
|
||||
const data = {
|
||||
count: 2,
|
||||
results: [
|
||||
{ id: 1, username: 'foo', url: 'item/1' },
|
||||
{ id: 2, username: 'bar', url: 'item/2' }
|
||||
]
|
||||
};
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
columns={columns}
|
||||
displayKey="username"
|
||||
onRowClick={handleRowClick}
|
||||
onSearch={() => {}}
|
||||
onSearch={() => ({ data })}
|
||||
selectedResourceRows={[]}
|
||||
sortedColumnKey="username"
|
||||
/>
|
||||
);
|
||||
const selectResourceStepWrapper = wrapper.find('SelectResourceStep');
|
||||
selectResourceStepWrapper.setState({
|
||||
resources: [
|
||||
{ id: 1, username: 'foo' }
|
||||
]
|
||||
});
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const checkboxListItemWrapper = wrapper.find('CheckboxListItem');
|
||||
expect(checkboxListItemWrapper.length).toBe(1);
|
||||
checkboxListItemWrapper.first().find('input[type="checkbox"]').simulate('change', { target: { checked: true } });
|
||||
expect(handleRowClick).toHaveBeenCalledWith({ id: 1, username: 'foo' });
|
||||
expect(checkboxListItemWrapper.length).toBe(2);
|
||||
checkboxListItemWrapper.first().find('input[type="checkbox"]')
|
||||
.simulate('change', { target: { checked: true } });
|
||||
expect(handleRowClick).toHaveBeenCalledWith(data.results[0]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -79,30 +79,31 @@ const defaultContexts = {
|
||||
dialog: {}
|
||||
};
|
||||
|
||||
const providers = {
|
||||
config: ConfigProvider,
|
||||
network: _NetworkProvider,
|
||||
dialog: RootDialogProvider,
|
||||
};
|
||||
|
||||
function wrapContexts (node, context) {
|
||||
let wrapped = node;
|
||||
let isFirst = true;
|
||||
Object.keys(providers).forEach(key => {
|
||||
if (context[key]) {
|
||||
const Provider = providers[key];
|
||||
wrapped = (
|
||||
<Provider
|
||||
value={context[key]}
|
||||
i18n={isFirst ? defaultContexts.linguiPublisher.i18n : null}
|
||||
>
|
||||
{wrapped}
|
||||
</Provider>
|
||||
const { config, network, dialog } = context;
|
||||
class Wrap extends React.Component {
|
||||
render () {
|
||||
// eslint-disable-next-line react/no-this-in-sfc
|
||||
const { children, ...props } = this.props;
|
||||
const component = React.cloneElement(children, props);
|
||||
return (
|
||||
<RootDialogProvider value={dialog}>
|
||||
<_NetworkProvider value={network}>
|
||||
<ConfigProvider
|
||||
value={config}
|
||||
i18n={defaultContexts.linguiPublisher.i18n}
|
||||
>
|
||||
{component}
|
||||
</ConfigProvider>
|
||||
</_NetworkProvider>
|
||||
</RootDialogProvider>
|
||||
);
|
||||
isFirst = false;
|
||||
}
|
||||
});
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrap>{node}</Wrap>
|
||||
);
|
||||
}
|
||||
|
||||
function applyDefaultContexts (context) {
|
||||
|
||||
@ -191,4 +191,19 @@ describe('mountWithContexts', () => {
|
||||
expect(dialog.setRootDialogMessage).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set props on wrapped component', () => {
|
||||
function Component ({ text }) {
|
||||
return (<div>{text}</div>);
|
||||
}
|
||||
|
||||
const wrapper = mountWithContexts(
|
||||
<Component text="foo" />
|
||||
);
|
||||
expect(wrapper.find('div').text()).toEqual('foo');
|
||||
wrapper.setProps({
|
||||
text: 'bar'
|
||||
});
|
||||
expect(wrapper.find('div').text()).toEqual('bar');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { mountWithContexts } from '../../../../enzymeHelpers';
|
||||
import { sleep } from '../../../../testUtils';
|
||||
import OrganizationTeams, { _OrganizationTeams } from '../../../../../src/pages/Organizations/screens/Organization/OrganizationTeams';
|
||||
@ -68,65 +66,14 @@ describe('<OrganizationTeams />', () => {
|
||||
const list = wrapper.find('PaginatedDataList');
|
||||
expect(list.prop('items')).toEqual(listData.data.results);
|
||||
expect(list.prop('itemCount')).toEqual(listData.data.count);
|
||||
expect(list.prop('queryParams')).toEqual({
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
});
|
||||
});
|
||||
|
||||
test('should pass queryParams to PaginatedDataList', async () => {
|
||||
const page1Data = listData;
|
||||
const page2Data = {
|
||||
data: {
|
||||
count: 7,
|
||||
results: [
|
||||
{ id: 6, name: 'six', url: '/org/team/6' },
|
||||
{ id: 7, name: 'seven', url: '/org/team/7' },
|
||||
]
|
||||
}
|
||||
};
|
||||
const readOrganizationTeamsList = jest.fn();
|
||||
readOrganizationTeamsList.mockReturnValueOnce(page1Data);
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/organizations/1/teams'],
|
||||
});
|
||||
const wrapper = mountWithContexts(
|
||||
<Router history={history}>
|
||||
<OrganizationTeams
|
||||
id={1}
|
||||
searchString=""
|
||||
/>
|
||||
</Router>,
|
||||
{ context: {
|
||||
network: {
|
||||
api: { readOrganizationTeamsList },
|
||||
handleHttpError: () => {}
|
||||
},
|
||||
router: false,
|
||||
} }
|
||||
);
|
||||
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
|
||||
const list = wrapper.find('PaginatedDataList');
|
||||
expect(list.prop('queryParams')).toEqual({
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
});
|
||||
|
||||
readOrganizationTeamsList.mockReturnValueOnce(page2Data);
|
||||
history.push('/organizations/1/teams?page=2');
|
||||
|
||||
await sleep(0);
|
||||
wrapper.update();
|
||||
const list2 = wrapper.find('PaginatedDataList');
|
||||
expect(list2.prop('queryParams')).toEqual({
|
||||
page: 2,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
expect(list.prop('qsConfig')).toEqual({
|
||||
namespace: 'team',
|
||||
defaultParams: {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
},
|
||||
integerFields: ['page', 'page_size'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -146,24 +146,6 @@ describe('<OrganizationsList />', () => {
|
||||
expect(fetchOrgs).toBeCalled();
|
||||
});
|
||||
|
||||
test('url updates properly', () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['organizations?order_by=name&page=1&page_size=5'],
|
||||
});
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationsList />, {
|
||||
context: { router: { history } }
|
||||
}
|
||||
);
|
||||
const component = wrapper.find('OrganizationsList');
|
||||
component.instance().updateUrl({
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'modified'
|
||||
});
|
||||
expect(history.location.search).toBe('?order_by=modified&page=1&page_size=5');
|
||||
});
|
||||
|
||||
test('error is thrown when org not successfully deleted from api', async () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['organizations?order_by=name&page=1&page_size=5'],
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
import { encodeQueryString, parseQueryString } from '../../src/util/qs';
|
||||
import {
|
||||
encodeQueryString,
|
||||
parseQueryString,
|
||||
getQSConfig,
|
||||
parseNamespacedQueryString,
|
||||
encodeNamespacedQueryString,
|
||||
updateNamespacedQueryString,
|
||||
} from '../../src/util/qs';
|
||||
|
||||
describe('qs (qs.js)', () => {
|
||||
test('encodeQueryString returns the expected queryString', () => {
|
||||
@ -23,27 +30,184 @@ describe('qs (qs.js)', () => {
|
||||
expect(encodeQueryString(vals)).toEqual('order_by=name');
|
||||
});
|
||||
|
||||
test('parseQueryString returns the expected queryParams', () => {
|
||||
[
|
||||
['order_by=name&page=1&page_size=5', ['page', 'page_size'], { order_by: 'name', page: 1, page_size: 5 }],
|
||||
['order_by=name&page=1&page_size=5', ['page_size'], { order_by: 'name', page: '1', page_size: 5 }],
|
||||
]
|
||||
.forEach(([queryString, integerFields, expectedQueryParams]) => {
|
||||
const actualQueryParams = parseQueryString(queryString, integerFields);
|
||||
describe('parseQueryString', () => {
|
||||
test('parseQueryString returns the expected queryParams', () => {
|
||||
[
|
||||
['order_by=name&page=1&page_size=5', ['page', 'page_size'], { order_by: 'name', page: 1, page_size: 5 }],
|
||||
['order_by=name&page=1&page_size=5', ['page_size'], { order_by: 'name', page: '1', page_size: 5 }],
|
||||
]
|
||||
.forEach(([queryString, integerFields, expectedQueryParams]) => {
|
||||
const actualQueryParams = parseQueryString(queryString, integerFields);
|
||||
|
||||
expect(actualQueryParams).toEqual(expectedQueryParams);
|
||||
});
|
||||
});
|
||||
|
||||
test('parseQueryString should strip leading "?"', () => {
|
||||
expect(parseQueryString('?foo=bar&order_by=win')).toEqual({
|
||||
foo: 'bar',
|
||||
order_by: 'win',
|
||||
expect(actualQueryParams).toEqual(expectedQueryParams);
|
||||
});
|
||||
});
|
||||
|
||||
expect(parseQueryString('foo=bar&order_by=?win')).toEqual({
|
||||
foo: 'bar',
|
||||
order_by: '?win',
|
||||
test('parseQueryString should strip leading "?"', () => {
|
||||
expect(parseQueryString('?foo=bar&order_by=win')).toEqual({
|
||||
foo: 'bar',
|
||||
order_by: 'win',
|
||||
});
|
||||
|
||||
expect(parseQueryString('foo=bar&order_by=?win')).toEqual({
|
||||
foo: 'bar',
|
||||
order_by: '?win',
|
||||
});
|
||||
});
|
||||
|
||||
test('should return empty object if no values', () => {
|
||||
expect(parseQueryString('')).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
test('should get default QS config object', () => {
|
||||
expect(getQSConfig('organization')).toEqual({
|
||||
namespace: 'organization',
|
||||
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields: ['page', 'page_size'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw if no namespace given', () => {
|
||||
expect(() => getQSConfig()).toThrow();
|
||||
});
|
||||
|
||||
test('should build configured QS config object', () => {
|
||||
const defaults = {
|
||||
page: 1,
|
||||
page_size: 15,
|
||||
};
|
||||
expect(getQSConfig('inventory', defaults)).toEqual({
|
||||
namespace: 'inventory',
|
||||
defaultParams: { page: 1, page_size: 15 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseNamespacedQueryString', () => {
|
||||
test('should get query params', () => {
|
||||
const config = {
|
||||
namespace: null,
|
||||
defaultParams: { page: 1, page_size: 15 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const query = '?foo=bar&page=3';
|
||||
expect(parseNamespacedQueryString(config, query)).toEqual({
|
||||
foo: 'bar',
|
||||
page: 3,
|
||||
page_size: 15,
|
||||
});
|
||||
});
|
||||
|
||||
test('should get query params with correct integer fields', () => {
|
||||
const config = {
|
||||
namespace: null,
|
||||
defaultParams: {},
|
||||
integerFields: ['page', 'foo'],
|
||||
};
|
||||
const query = '?foo=4&bar=5';
|
||||
expect(parseNamespacedQueryString(config, query)).toEqual({
|
||||
foo: 4,
|
||||
bar: '5',
|
||||
});
|
||||
});
|
||||
|
||||
test('should get namespaced query params', () => {
|
||||
const config = {
|
||||
namespace: 'inventory',
|
||||
defaultParams: { page: 1, page_size: 5 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const query = '?inventory.page=2&inventory.order_by=name&other=15';
|
||||
expect(parseNamespacedQueryString(config, query)).toEqual({
|
||||
page: 2,
|
||||
order_by: 'name',
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
|
||||
test('should exclude other namespaced query params', () => {
|
||||
const config = {
|
||||
namespace: 'inventory',
|
||||
defaultParams: { page: 1, page_size: 5 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const query = '?inventory.page=2&inventory.order_by=name&foo.other=15';
|
||||
expect(parseNamespacedQueryString(config, query)).toEqual({
|
||||
page: 2,
|
||||
order_by: 'name',
|
||||
page_size: 5,
|
||||
});
|
||||
});
|
||||
|
||||
test('should exclude defaults if includeDefaults is false', () => {
|
||||
const config = {
|
||||
namespace: null,
|
||||
defaultParams: { page: 1, page_size: 15 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const query = '?foo=bar&page=3';
|
||||
expect(parseNamespacedQueryString(config, query, false)).toEqual({
|
||||
foo: 'bar',
|
||||
page: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('encodeNamespacedQueryString', () => {
|
||||
test('should encode params without namespace', () => {
|
||||
const config = {
|
||||
namespace: null,
|
||||
defaultParams: { page: 1, page_size: 5 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const params = {
|
||||
page: 1,
|
||||
order_by: 'name',
|
||||
};
|
||||
const qs = 'order_by=name&page=1';
|
||||
expect(encodeNamespacedQueryString(config, params)).toEqual(qs);
|
||||
});
|
||||
|
||||
test('should encode params with namespace', () => {
|
||||
const config = {
|
||||
namespace: 'inventory',
|
||||
defaultParams: { page: 1, page_size: 5 },
|
||||
integerFields: ['page', 'page_size'],
|
||||
};
|
||||
const params = {
|
||||
page: 1,
|
||||
order_by: 'name',
|
||||
};
|
||||
const qs = 'inventory.order_by=name&inventory.page=1';
|
||||
expect(encodeNamespacedQueryString(config, params)).toEqual(qs);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateNamespacedQueryString', () => {
|
||||
test('should return current values', () => {
|
||||
const qs = '?foo=bar&inventory.page=1';
|
||||
const updated = updateNamespacedQueryString({}, qs, {});
|
||||
expect(updated).toEqual('foo=bar&inventory.page=1');
|
||||
});
|
||||
|
||||
test('should update new values', () => {
|
||||
const qs = '?foo=bar&inventory.page=1';
|
||||
const updated = updateNamespacedQueryString({}, qs, { foo: 'baz' });
|
||||
expect(updated).toEqual('foo=baz&inventory.page=1');
|
||||
});
|
||||
|
||||
test('should add new values', () => {
|
||||
const qs = '?foo=bar&inventory.page=1';
|
||||
const updated = updateNamespacedQueryString({}, qs, { page: 5 });
|
||||
expect(updated).toEqual('foo=bar&inventory.page=1&page=5');
|
||||
});
|
||||
|
||||
test('should update namespaced values', () => {
|
||||
const qs = '?foo=bar&inventory.page=1';
|
||||
const config = { namespace: 'inventory' };
|
||||
const updated = updateNamespacedQueryString(config, qs, { page: 2 });
|
||||
expect(updated).toEqual('foo=bar&inventory.page=2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -186,24 +186,22 @@ class AddResourceRole extends React.Component {
|
||||
<SelectResourceStep
|
||||
columns={userColumns}
|
||||
displayKey="username"
|
||||
emptyListBody={i18n._(t`Please add users to populate this list`)}
|
||||
emptyListTitle={i18n._(t`No Users Found`)}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={this.readUsers}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
sortedColumnKey="username"
|
||||
itemName="user"
|
||||
/>
|
||||
)}
|
||||
{selectedResource === 'teams' && (
|
||||
<SelectResourceStep
|
||||
columns={teamColumns}
|
||||
emptyListBody={i18n._(t`Please add teams to populate this list`)}
|
||||
emptyListTitle={i18n._(t`No Teams Found`)}
|
||||
onRowClick={this.handleResourceCheckboxClick}
|
||||
onSearch={this.readTeams}
|
||||
selectedLabel={i18n._(t`Selected`)}
|
||||
selectedResourceRows={selectedResourceRows}
|
||||
itemName="team"
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@ -1,19 +1,11 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { i18nMark } from '@lingui/react';
|
||||
import {
|
||||
EmptyState,
|
||||
EmptyStateBody,
|
||||
EmptyStateIcon,
|
||||
Title,
|
||||
DataList,
|
||||
} from '@patternfly/react-core';
|
||||
import { CubesIcon } from '@patternfly/react-icons';
|
||||
|
||||
import PaginatedDataList from '../PaginatedDataList';
|
||||
import CheckboxListItem from '../ListItem';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import Pagination from '../Pagination';
|
||||
import SelectedList from '../SelectedList';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';
|
||||
|
||||
const paginationStyling = {
|
||||
paddingLeft: '0',
|
||||
@ -27,163 +19,113 @@ class SelectResourceStep extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { sortedColumnKey } = this.props;
|
||||
|
||||
this.state = {
|
||||
isInitialized: false,
|
||||
count: null,
|
||||
error: false,
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
resources: [],
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey
|
||||
};
|
||||
|
||||
this.handleSetPage = this.handleSetPage.bind(this);
|
||||
this.handleSort = this.handleSort.bind(this);
|
||||
this.readResourceList = this.readResourceList.bind(this);
|
||||
this.qsConfig = getQSConfig('resource', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: props.sortedColumnKey,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { page_size, page, sortedColumnKey } = this.state;
|
||||
|
||||
this.readResourceList({ page_size, page, order_by: sortedColumnKey });
|
||||
this.readResourceList();
|
||||
}
|
||||
|
||||
handleSetPage (pageNumber) {
|
||||
const { page_size, sortedColumnKey, sortOrder } = this.state;
|
||||
const page = parseInt(pageNumber, 10);
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.readResourceList();
|
||||
}
|
||||
|
||||
this.readResourceList({ page_size, page, order_by });
|
||||
}
|
||||
|
||||
handleSort (sortedColumnKey, sortOrder) {
|
||||
const { page_size } = this.state;
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
this.readResourceList({ page: 1, page_size, order_by });
|
||||
}
|
||||
|
||||
async readResourceList (queryParams) {
|
||||
const { onSearch } = this.props;
|
||||
const { page, order_by } = queryParams;
|
||||
|
||||
let sortOrder = 'ascending';
|
||||
let sortedColumnKey = order_by;
|
||||
|
||||
if (order_by.startsWith('-')) {
|
||||
sortOrder = 'descending';
|
||||
sortedColumnKey = order_by.substring(1);
|
||||
}
|
||||
|
||||
this.setState({ error: false });
|
||||
async readResourceList () {
|
||||
const { onSearch, location } = this.props;
|
||||
const queryParams = parseNamespacedQueryString(this.qsConfig, location.search);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
error: false,
|
||||
});
|
||||
try {
|
||||
const { data } = await onSearch(queryParams);
|
||||
const { count, results } = data;
|
||||
|
||||
const stateToUpdate = {
|
||||
count,
|
||||
page,
|
||||
this.setState({
|
||||
resources: results,
|
||||
sortOrder,
|
||||
sortedColumnKey
|
||||
};
|
||||
|
||||
this.setState(stateToUpdate);
|
||||
count,
|
||||
isInitialized: true,
|
||||
isLoading: false,
|
||||
error: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({ error: true });
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
error: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
isInitialized,
|
||||
isLoading,
|
||||
count,
|
||||
error,
|
||||
page,
|
||||
page_size,
|
||||
resources,
|
||||
sortOrder,
|
||||
sortedColumnKey
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
columns,
|
||||
displayKey,
|
||||
emptyListBody,
|
||||
emptyListTitle,
|
||||
onRowClick,
|
||||
selectedLabel,
|
||||
selectedResourceRows
|
||||
selectedResourceRows,
|
||||
itemName,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Fragment>
|
||||
{(resources.length === 0) ? (
|
||||
<EmptyState>
|
||||
<EmptyStateIcon icon={CubesIcon} />
|
||||
<Title size="lg">
|
||||
{emptyListTitle}
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
{emptyListBody}
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
) : (
|
||||
<Fragment>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={displayKey}
|
||||
label={selectedLabel}
|
||||
onRemove={onRowClick}
|
||||
selected={selectedResourceRows}
|
||||
showOverflowAfter={5}
|
||||
{isLoading && (<div>Loading...</div>)}
|
||||
{isInitialized && (
|
||||
<Fragment>
|
||||
{selectedResourceRows.length > 0 && (
|
||||
<SelectedList
|
||||
displayKey={displayKey}
|
||||
label={selectedLabel}
|
||||
onRemove={onRowClick}
|
||||
selected={selectedResourceRows}
|
||||
showOverflowAfter={5}
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
items={resources}
|
||||
itemCount={count}
|
||||
itemName={itemName}
|
||||
qsConfig={this.qsConfig}
|
||||
toolbarColumns={
|
||||
columns
|
||||
}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(i => i.id === item.id)}
|
||||
itemId={item.id}
|
||||
key={item.id}
|
||||
name={item[displayKey]}
|
||||
onSelect={() => onRowClick(item)}
|
||||
/>
|
||||
)}
|
||||
<DataListToolbar
|
||||
columns={columns}
|
||||
noLeftMargin
|
||||
onSearch={this.onSearch}
|
||||
handleSort={this.handleSort}
|
||||
sortOrder={sortOrder}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
/>
|
||||
<DataList aria-label={i18nMark('Roles List')}>
|
||||
{resources.map(i => (
|
||||
<CheckboxListItem
|
||||
isSelected={selectedResourceRows.some(item => item.id === i.id)}
|
||||
itemId={i.id}
|
||||
key={i.id}
|
||||
name={i[displayKey]}
|
||||
onSelect={() => onRowClick(i)}
|
||||
/>
|
||||
))}
|
||||
</DataList>
|
||||
<Pagination
|
||||
count={count}
|
||||
onSetPage={this.handleSetPage}
|
||||
page={page}
|
||||
pageCount={Math.ceil(count / page_size)}
|
||||
pageSizeOptions={null}
|
||||
page_size={page_size}
|
||||
showPageSizeOptions={false}
|
||||
style={paginationStyling}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
alignToolbarLeft
|
||||
showPageSizeOptions={false}
|
||||
paginationStyling={paginationStyling}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
{ error ? <div>error</div> : '' }
|
||||
</Fragment>
|
||||
);
|
||||
@ -193,23 +135,22 @@ class SelectResourceStep extends React.Component {
|
||||
SelectResourceStep.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
displayKey: PropTypes.string,
|
||||
emptyListBody: PropTypes.string,
|
||||
emptyListTitle: PropTypes.string,
|
||||
onRowClick: PropTypes.func,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
selectedLabel: PropTypes.string,
|
||||
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||
sortedColumnKey: PropTypes.string
|
||||
sortedColumnKey: PropTypes.string,
|
||||
itemName: PropTypes.string,
|
||||
};
|
||||
|
||||
SelectResourceStep.defaultProps = {
|
||||
displayKey: 'name',
|
||||
emptyListBody: i18nMark('Please add items to populate this list'),
|
||||
emptyListTitle: i18nMark('No Items Found'),
|
||||
onRowClick: () => {},
|
||||
selectedLabel: i18nMark('Selected Items'),
|
||||
selectedResourceRows: [],
|
||||
sortedColumnKey: 'name'
|
||||
sortedColumnKey: 'name',
|
||||
itemName: 'item',
|
||||
};
|
||||
|
||||
export default SelectResourceStep;
|
||||
export { SelectResourceStep as _SelectResourceStep };
|
||||
export default withRouter(SelectResourceStep);
|
||||
|
||||
@ -1,26 +1,22 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { SearchIcon, CubesIcon } from '@patternfly/react-icons';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { SearchIcon } from '@patternfly/react-icons';
|
||||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Chip,
|
||||
EmptyState,
|
||||
EmptyStateBody,
|
||||
EmptyStateIcon,
|
||||
InputGroup,
|
||||
Modal,
|
||||
Title
|
||||
} from '@patternfly/react-core';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { withNetwork } from '../../contexts/Network';
|
||||
|
||||
import PaginatedDataList from '../PaginatedDataList';
|
||||
import CheckboxListItem from '../ListItem';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import SelectedList from '../SelectedList';
|
||||
import Pagination from '../Pagination';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../util/qs';
|
||||
|
||||
const paginationStyling = {
|
||||
paddingLeft: '0',
|
||||
@ -39,71 +35,48 @@ class Lookup extends React.Component {
|
||||
lookupSelectedItems: [...props.value] || [],
|
||||
results: [],
|
||||
count: 0,
|
||||
error: null,
|
||||
};
|
||||
this.qsConfig = getQSConfig('lookup', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
error: null,
|
||||
sortOrder: 'ascending',
|
||||
sortedColumnKey: props.sortedColumnKey
|
||||
};
|
||||
this.onSetPage = this.onSetPage.bind(this);
|
||||
order_by: props.sortedColumnKey,
|
||||
});
|
||||
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||
this.toggleSelected = this.toggleSelected.bind(this);
|
||||
this.saveModal = this.saveModal.bind(this);
|
||||
this.getData = this.getData.bind(this);
|
||||
this.onSearch = this.onSearch.bind(this);
|
||||
this.onSort = this.onSort.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { page_size, page } = this.state;
|
||||
this.getData({ page_size, page });
|
||||
this.getData();
|
||||
}
|
||||
|
||||
onSearch () {
|
||||
const { sortedColumnKey, sortOrder } = this.state;
|
||||
this.onSort(sortedColumnKey, sortOrder);
|
||||
}
|
||||
|
||||
onSort (sortedColumnKey, sortOrder) {
|
||||
this.setState({ page: 1, sortedColumnKey, sortOrder }, this.getData);
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props;
|
||||
if (location !== prevProps.location) {
|
||||
this.getData();
|
||||
}
|
||||
}
|
||||
|
||||
async getData () {
|
||||
const { getItems, handleHttpError } = this.props;
|
||||
const { page, page_size, sortedColumnKey, sortOrder } = this.state;
|
||||
const { getItems, handleHttpError, location } = this.props;
|
||||
const queryParams = parseNamespacedQueryString(this.qsConfig, location.search);
|
||||
|
||||
this.setState({ error: false });
|
||||
|
||||
const queryParams = {
|
||||
page,
|
||||
page_size
|
||||
};
|
||||
|
||||
if (sortedColumnKey) {
|
||||
queryParams.order_by = sortOrder === 'descending' ? `-${sortedColumnKey}` : sortedColumnKey;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await getItems(queryParams);
|
||||
const { results, count } = data;
|
||||
|
||||
const stateToUpdate = {
|
||||
this.setState({
|
||||
results,
|
||||
count
|
||||
};
|
||||
|
||||
this.setState(stateToUpdate);
|
||||
});
|
||||
} catch (err) {
|
||||
handleHttpError(err) || this.setState({ error: true });
|
||||
}
|
||||
}
|
||||
|
||||
onSetPage = async (pageNumber, pageSize) => {
|
||||
const page = parseInt(pageNumber, 10);
|
||||
const page_size = parseInt(pageSize, 10);
|
||||
this.setState({ page, page_size }, this.getData);
|
||||
};
|
||||
|
||||
toggleSelected (row) {
|
||||
const { name, onLookupSave } = this.props;
|
||||
const { lookupSelectedItems: updatedSelectedItems, isModalOpen } = this.state;
|
||||
@ -156,10 +129,6 @@ class Lookup extends React.Component {
|
||||
error,
|
||||
results,
|
||||
count,
|
||||
page,
|
||||
page_size,
|
||||
sortedColumnKey,
|
||||
sortOrder
|
||||
} = this.state;
|
||||
const { id, lookupHeader = 'items', value, columns } = this.props;
|
||||
|
||||
@ -200,49 +169,25 @@ class Lookup extends React.Component {
|
||||
<Button key="cancel" variant="secondary" onClick={this.handleModalToggle}>{(results.length === 0) ? i18n._(t`Close`) : i18n._(t`Cancel`)}</Button>
|
||||
]}
|
||||
>
|
||||
{(results.length === 0) ? (
|
||||
<EmptyState>
|
||||
<EmptyStateIcon icon={CubesIcon} />
|
||||
<Title size="lg">
|
||||
<Trans>{`No ${lookupHeader} Found`}</Trans>
|
||||
</Title>
|
||||
<EmptyStateBody>
|
||||
<Trans>{`Please add ${lookupHeader.toLowerCase()} to populate this list`}</Trans>
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
) : (
|
||||
<Fragment>
|
||||
<DataListToolbar
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
sortOrder={sortOrder}
|
||||
columns={columns}
|
||||
onSearch={this.onSearch}
|
||||
onSort={this.onSort}
|
||||
noLeftMargin
|
||||
<PaginatedDataList
|
||||
items={results}
|
||||
itemCount={count}
|
||||
itemName={lookupHeader}
|
||||
qsConfig={this.qsConfig}
|
||||
toolbarColumns={columns}
|
||||
renderItem={item => (
|
||||
<CheckboxListItem
|
||||
key={item.id}
|
||||
itemId={item.id}
|
||||
name={item.name}
|
||||
isSelected={lookupSelectedItems.some(i => i.id === item.id)}
|
||||
onSelect={() => this.toggleSelected(item)}
|
||||
/>
|
||||
<ul className="pf-c-data-list awx-c-list">
|
||||
{results.map(i => (
|
||||
<CheckboxListItem
|
||||
key={i.id}
|
||||
itemId={i.id}
|
||||
name={i.name}
|
||||
isSelected={lookupSelectedItems.some(item => item.id === i.id)}
|
||||
onSelect={() => this.toggleSelected(i)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Pagination
|
||||
count={count}
|
||||
page={page}
|
||||
pageCount={Math.ceil(count / page_size)}
|
||||
page_size={page_size}
|
||||
onSetPage={this.onSetPage}
|
||||
pageSizeOptions={null}
|
||||
showPageSizeOptions={false}
|
||||
style={paginationStyling}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
)}
|
||||
alignToolbarLeft
|
||||
showPageSizeOptions={false}
|
||||
paginationStyling={paginationStyling}
|
||||
/>
|
||||
{lookupSelectedItems.length > 0 && (
|
||||
<SelectedList
|
||||
label={i18n._(t`Selected`)}
|
||||
@ -264,9 +209,10 @@ Lookup.propTypes = {
|
||||
id: PropTypes.string,
|
||||
getItems: PropTypes.func.isRequired,
|
||||
lookupHeader: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
name: PropTypes.string, // TODO: delete, unused ?
|
||||
onLookupSave: PropTypes.func.isRequired,
|
||||
value: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortedColumnKey: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
Lookup.defaultProps = {
|
||||
@ -276,4 +222,4 @@ Lookup.defaultProps = {
|
||||
};
|
||||
|
||||
export { Lookup as _Lookup };
|
||||
export default withNetwork(Lookup);
|
||||
export default withNetwork(withRouter(Lookup));
|
||||
|
||||
@ -20,8 +20,12 @@ import { withRouter, Link } from 'react-router-dom';
|
||||
|
||||
import Pagination from '../Pagination';
|
||||
import DataListToolbar from '../DataListToolbar';
|
||||
import { encodeQueryString, parseQueryString } from '../../util/qs';
|
||||
import {
|
||||
parseNamespacedQueryString,
|
||||
updateNamespacedQueryString,
|
||||
} from '../../util/qs';
|
||||
import { pluralize, getArticle, ucFirst } from '../../util/strings';
|
||||
import { QSConfig } from '../../types';
|
||||
|
||||
const detailWrapperStyle = {
|
||||
display: 'grid',
|
||||
@ -47,12 +51,14 @@ class PaginatedDataList extends React.Component {
|
||||
}
|
||||
|
||||
getPageCount () {
|
||||
const { itemCount, queryParams: { page_size } } = this.props;
|
||||
return Math.ceil(itemCount / page_size);
|
||||
const { itemCount, qsConfig, location } = this.props;
|
||||
const queryParams = parseNamespacedQueryString(qsConfig, location.search);
|
||||
return Math.ceil(itemCount / queryParams.page_size);
|
||||
}
|
||||
|
||||
getSortOrder () {
|
||||
const { queryParams } = this.props;
|
||||
const { qsConfig, location } = this.props;
|
||||
const queryParams = parseNamespacedQueryString(qsConfig, location.search);
|
||||
if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
|
||||
return [queryParams.order_by.substr(1), 'descending'];
|
||||
}
|
||||
@ -74,13 +80,9 @@ class PaginatedDataList extends React.Component {
|
||||
}
|
||||
|
||||
pushHistoryState (newParams) {
|
||||
const { history } = this.props;
|
||||
const { history, qsConfig } = this.props;
|
||||
const { pathname, search } = history.location;
|
||||
const currentParams = parseQueryString(search);
|
||||
const qs = encodeQueryString({
|
||||
...currentParams,
|
||||
...newParams
|
||||
});
|
||||
const qs = updateNamespacedQueryString(qsConfig, search, newParams);
|
||||
history.push(`${pathname}?${qs}`);
|
||||
}
|
||||
|
||||
@ -93,7 +95,7 @@ class PaginatedDataList extends React.Component {
|
||||
const {
|
||||
items,
|
||||
itemCount,
|
||||
queryParams,
|
||||
qsConfig,
|
||||
renderItem,
|
||||
toolbarColumns,
|
||||
additionalControls,
|
||||
@ -102,9 +104,14 @@ class PaginatedDataList extends React.Component {
|
||||
showSelectAll,
|
||||
isAllSelected,
|
||||
onSelectAll,
|
||||
alignToolbarLeft,
|
||||
showPageSizeOptions,
|
||||
paginationStyling,
|
||||
location,
|
||||
} = this.props;
|
||||
const { error } = this.state;
|
||||
const [orderBy, sortOrder] = this.getSortOrder();
|
||||
const queryParams = parseNamespacedQueryString(qsConfig, location.search);
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@ -153,6 +160,7 @@ class PaginatedDataList extends React.Component {
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={onSelectAll}
|
||||
additionalControls={additionalControls}
|
||||
noLeftMargin={alignToolbarLeft}
|
||||
/>
|
||||
<DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}>
|
||||
{items.map(item => (renderItem ? renderItem(item) : (
|
||||
@ -182,10 +190,12 @@ class PaginatedDataList extends React.Component {
|
||||
</DataList>
|
||||
<Pagination
|
||||
count={itemCount}
|
||||
page={queryParams.page}
|
||||
page={queryParams.page || 1}
|
||||
pageCount={this.getPageCount()}
|
||||
page_size={queryParams.page_size}
|
||||
onSetPage={this.handleSetPage}
|
||||
showPageSizeOptions={showPageSizeOptions}
|
||||
style={paginationStyling}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
@ -202,19 +212,12 @@ const Item = PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
});
|
||||
|
||||
const QueryParams = PropTypes.shape({
|
||||
page: PropTypes.number,
|
||||
page_size: PropTypes.number,
|
||||
order_by: PropTypes.string,
|
||||
});
|
||||
|
||||
PaginatedDataList.propTypes = {
|
||||
items: PropTypes.arrayOf(Item).isRequired,
|
||||
itemCount: PropTypes.number.isRequired,
|
||||
itemName: PropTypes.string,
|
||||
itemNamePlural: PropTypes.string,
|
||||
// TODO: determine this internally but pass in defaults?
|
||||
queryParams: QueryParams.isRequired,
|
||||
qsConfig: QSConfig.isRequired,
|
||||
renderItem: PropTypes.func,
|
||||
toolbarColumns: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
@ -225,6 +228,9 @@ PaginatedDataList.propTypes = {
|
||||
showSelectAll: PropTypes.bool,
|
||||
isAllSelected: PropTypes.bool,
|
||||
onSelectAll: PropTypes.func,
|
||||
alignToolbarLeft: PropTypes.bool,
|
||||
showPageSizeOptions: PropTypes.bool,
|
||||
paginationStyling: PropTypes.shape(),
|
||||
};
|
||||
|
||||
PaginatedDataList.defaultProps = {
|
||||
@ -238,6 +244,9 @@ PaginatedDataList.defaultProps = {
|
||||
showSelectAll: false,
|
||||
isAllSelected: false,
|
||||
onSelectAll: null,
|
||||
alignToolbarLeft: false,
|
||||
showPageSizeOptions: true,
|
||||
paginationStyling: null,
|
||||
};
|
||||
|
||||
export { PaginatedDataList as _PaginatedDataList };
|
||||
|
||||
@ -6,14 +6,14 @@ import OrganizationAccessItem from '../../components/OrganizationAccessItem';
|
||||
import DeleteRoleConfirmationModal from '../../components/DeleteRoleConfirmationModal';
|
||||
import AddResourceRole from '../../../../components/AddRole/AddResourceRole';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import { parseQueryString } from '../../../../util/qs';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
import { Organization } from '../../../../types';
|
||||
|
||||
const DEFAULT_QUERY_PARAMS = {
|
||||
const QS_CONFIG = getQSConfig('access', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'first_name',
|
||||
};
|
||||
});
|
||||
|
||||
class OrganizationAccess extends React.Component {
|
||||
static propTypes = {
|
||||
@ -54,12 +54,12 @@ class OrganizationAccess extends React.Component {
|
||||
}
|
||||
|
||||
async readOrgAccessList () {
|
||||
const { organization, api, handleHttpError } = this.props;
|
||||
const { organization, api, handleHttpError, location } = this.props;
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
const { data } = await api.getOrganizationAccessList(
|
||||
organization.id,
|
||||
this.getQueryParams()
|
||||
parseNamespacedQueryString(QS_CONFIG, location.search)
|
||||
);
|
||||
this.setState({
|
||||
itemCount: data.count || 0,
|
||||
@ -75,16 +75,6 @@ class OrganizationAccess extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getQueryParams () {
|
||||
const { location } = this.props;
|
||||
const searchParams = parseQueryString(location.search.substring(1));
|
||||
|
||||
return {
|
||||
...DEFAULT_QUERY_PARAMS,
|
||||
...searchParams,
|
||||
};
|
||||
}
|
||||
|
||||
confirmRemoveRole (role, accessRecord) {
|
||||
this.setState({
|
||||
roleToDelete: role,
|
||||
@ -175,7 +165,7 @@ class OrganizationAccess extends React.Component {
|
||||
items={accessRecords}
|
||||
itemCount={itemCount}
|
||||
itemName="role"
|
||||
queryParams={this.getQueryParams()}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={[
|
||||
{ name: i18nMark('Name'), key: 'first_name', isSortable: true },
|
||||
{ name: i18nMark('Username'), key: 'username', isSortable: true },
|
||||
|
||||
@ -4,13 +4,13 @@ import { withRouter } from 'react-router-dom';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
import PaginatedDataList from '../../../../components/PaginatedDataList';
|
||||
import NotificationListItem from '../../../../components/NotificationsList/NotificationListItem';
|
||||
import { parseQueryString } from '../../../../util/qs';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
|
||||
const DEFAULT_QUERY_PARAMS = {
|
||||
const QS_CONFIG = getQSConfig('notification', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
};
|
||||
});
|
||||
|
||||
const COLUMNS = [
|
||||
{ key: 'name', name: 'Name', isSortable: true },
|
||||
@ -48,19 +48,9 @@ class OrganizationNotifications extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getQueryParams () {
|
||||
const { location } = this.props;
|
||||
const searchParams = parseQueryString(location.search.substring(1));
|
||||
|
||||
return {
|
||||
...DEFAULT_QUERY_PARAMS,
|
||||
...searchParams,
|
||||
};
|
||||
}
|
||||
|
||||
async readNotifications () {
|
||||
const { api, handleHttpError, id } = this.props;
|
||||
const params = this.getQueryParams();
|
||||
const { id, api, handleHttpError, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
this.setState({ isLoading: true });
|
||||
try {
|
||||
const { data } = await api.getOrganizationNotifications(id, params);
|
||||
@ -191,7 +181,7 @@ class OrganizationNotifications extends Component {
|
||||
items={notifications}
|
||||
itemCount={itemCount}
|
||||
itemName="notification"
|
||||
queryParams={this.getQueryParams()}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={COLUMNS}
|
||||
renderItem={(notification) => (
|
||||
<NotificationListItem
|
||||
|
||||
@ -2,14 +2,14 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import PaginatedDataList from '../../../../components/PaginatedDataList';
|
||||
import { parseQueryString } from '../../../../util/qs';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
|
||||
import { withNetwork } from '../../../../contexts/Network';
|
||||
|
||||
const DEFAULT_QUERY_PARAMS = {
|
||||
const QS_CONFIG = getQSConfig('team', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
};
|
||||
});
|
||||
|
||||
class OrganizationTeams extends React.Component {
|
||||
constructor (props) {
|
||||
@ -37,19 +37,9 @@ class OrganizationTeams extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getQueryParams () {
|
||||
const { location } = this.props;
|
||||
const searchParams = parseQueryString(location.search.substring(1));
|
||||
|
||||
return {
|
||||
...DEFAULT_QUERY_PARAMS,
|
||||
...searchParams,
|
||||
};
|
||||
}
|
||||
|
||||
async readOrganizationTeamsList () {
|
||||
const { api, handleHttpError, id } = this.props;
|
||||
const params = this.getQueryParams();
|
||||
const { id, api, handleHttpError, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
this.setState({ isLoading: true, error: null });
|
||||
try {
|
||||
const {
|
||||
@ -86,7 +76,7 @@ class OrganizationTeams extends React.Component {
|
||||
items={teams}
|
||||
itemCount={itemCount}
|
||||
itemName="team"
|
||||
queryParams={this.getQueryParams()}
|
||||
qsConfig={QS_CONFIG}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@ -13,7 +13,7 @@ import PaginatedDataList, {
|
||||
ToolbarAddButton
|
||||
} from '../../../components/PaginatedDataList';
|
||||
import OrganizationListItem from '../components/OrganizationListItem';
|
||||
import { encodeQueryString, parseQueryString } from '../../../util/qs';
|
||||
import { getQSConfig, parseNamespacedQueryString } from '../../../util/qs';
|
||||
|
||||
const COLUMNS = [
|
||||
{ name: i18nMark('Name'), key: 'name', isSortable: true },
|
||||
@ -21,11 +21,11 @@ const COLUMNS = [
|
||||
{ name: i18nMark('Created'), key: 'created', isSortable: true, isNumeric: true },
|
||||
];
|
||||
|
||||
const DEFAULT_QUERY_PARAMS = {
|
||||
const QS_CONFIG = getQSConfig('organization', {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
};
|
||||
});
|
||||
|
||||
class OrganizationsList extends Component {
|
||||
constructor (props) {
|
||||
@ -39,10 +39,8 @@ class OrganizationsList extends Component {
|
||||
selected: [],
|
||||
};
|
||||
|
||||
this.getQueryParams = this.getQueryParams.bind(this);
|
||||
this.handleSelectAll = this.handleSelectAll.bind(this);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.updateUrl = this.updateUrl.bind(this);
|
||||
this.fetchOptionsOrganizations = this.fetchOptionsOrganizations.bind(this);
|
||||
this.fetchOrganizations = this.fetchOrganizations.bind(this);
|
||||
this.handleOrgDelete = this.handleOrgDelete.bind(this);
|
||||
@ -77,16 +75,6 @@ class OrganizationsList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getQueryParams () {
|
||||
const { location } = this.props;
|
||||
const searchParams = parseQueryString(location.search.substring(1));
|
||||
|
||||
return {
|
||||
...DEFAULT_QUERY_PARAMS,
|
||||
...searchParams,
|
||||
};
|
||||
}
|
||||
|
||||
async handleOrgDelete () {
|
||||
const { selected } = this.state;
|
||||
const { api, handleHttpError } = this.props;
|
||||
@ -101,25 +89,14 @@ class OrganizationsList extends Component {
|
||||
errorHandled = handleHttpError(err);
|
||||
} finally {
|
||||
if (!errorHandled) {
|
||||
const queryParams = this.getQueryParams();
|
||||
this.fetchOrganizations(queryParams);
|
||||
this.fetchOrganizations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateUrl (queryParams) {
|
||||
const { history, location } = this.props;
|
||||
const pathname = '/organizations';
|
||||
const search = `?${encodeQueryString(queryParams)}`;
|
||||
|
||||
if (search !== location.search) {
|
||||
history.replace({ pathname, search });
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOrganizations () {
|
||||
const { api, handleHttpError } = this.props;
|
||||
const params = this.getQueryParams();
|
||||
const { api, handleHttpError, location } = this.props;
|
||||
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
|
||||
|
||||
this.setState({ error: false, isLoading: true });
|
||||
|
||||
@ -185,7 +162,7 @@ class OrganizationsList extends Component {
|
||||
items={organizations}
|
||||
itemCount={itemCount}
|
||||
itemName="organization"
|
||||
queryParams={this.getQueryParams()}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarColumns={COLUMNS}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
|
||||
@ -47,3 +47,9 @@ export const Organization = shape({
|
||||
created: string,
|
||||
modified: string,
|
||||
});
|
||||
|
||||
export const QSConfig = shape({
|
||||
defaultParams: shape().isRequired,
|
||||
namespace: string,
|
||||
integerFields: arrayOf(string).isRequired,
|
||||
});
|
||||
|
||||
@ -40,3 +40,74 @@ export const parseQueryString = (queryString, integerFields = ['page', 'page_siz
|
||||
|
||||
return Object.assign(...keyValuePairs.map(([k, v]) => ({ [k]: v })));
|
||||
};
|
||||
|
||||
export function getQSConfig (
|
||||
namespace,
|
||||
defaultParams = { page: 1, page_size: 5, order_by: 'name' },
|
||||
integerFields = ['page', 'page_size']
|
||||
) {
|
||||
if (!namespace) {
|
||||
throw new Error('a QS namespace is required');
|
||||
}
|
||||
return {
|
||||
defaultParams,
|
||||
namespace,
|
||||
integerFields,
|
||||
};
|
||||
}
|
||||
|
||||
export function encodeNamespacedQueryString (config, params) {
|
||||
return encodeQueryString(namespaceParams(config.namespace, params));
|
||||
}
|
||||
|
||||
export function parseNamespacedQueryString (config, queryString, includeDefaults = true) {
|
||||
const integerFields = prependNamespaceToArray(config.namespace, config.integerFields);
|
||||
const parsed = parseQueryString(queryString, integerFields);
|
||||
|
||||
const namespace = {};
|
||||
Object.keys(parsed).forEach(field => {
|
||||
if (namespaceMatches(config.namespace, field)) {
|
||||
let fieldname = field;
|
||||
if (config.namespace) {
|
||||
fieldname = field.substr(config.namespace.length + 1);
|
||||
}
|
||||
namespace[fieldname] = parsed[field];
|
||||
}
|
||||
});
|
||||
return {
|
||||
...includeDefaults ? config.defaultParams : {},
|
||||
...namespace,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateNamespacedQueryString (config, queryString, newParams) {
|
||||
const params = parseQueryString(queryString);
|
||||
return encodeQueryString({
|
||||
...params,
|
||||
...namespaceParams(config.namespace, newParams),
|
||||
});
|
||||
}
|
||||
|
||||
function namespaceParams (ns, params) {
|
||||
if (!ns) return params;
|
||||
|
||||
const namespaced = {};
|
||||
Object.keys(params).forEach(key => {
|
||||
namespaced[`${ns}.${key}`] = params[key];
|
||||
});
|
||||
return namespaced;
|
||||
}
|
||||
|
||||
function namespaceMatches (namespace, fieldname) {
|
||||
if (!namespace) {
|
||||
return !fieldname.includes('.');
|
||||
}
|
||||
return fieldname.startsWith(`${namespace}.`);
|
||||
}
|
||||
|
||||
function prependNamespaceToArray (namespace, arr) {
|
||||
if (!namespace) {
|
||||
return arr;
|
||||
}
|
||||
return arr.map(f => `${namespace}.${f}`);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user