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