mirror of
https://github.com/ansible/awx.git
synced 2026-03-20 18:37:39 -02:30
Add roles modal to org access list
This commit is contained in:
260
__tests__/components/AddResourceRole.test.jsx
Normal file
260
__tests__/components/AddResourceRole.test.jsx
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import AddResourceRole from '../../src/components/AddRole/AddResourceRole';
|
||||||
|
|
||||||
|
describe('<SelectResourceStep />', () => {
|
||||||
|
const readUsers = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 2,
|
||||||
|
results: [
|
||||||
|
{ id: 1, username: 'foo' },
|
||||||
|
{ id: 2, username: 'bar' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const readTeams = jest.fn();
|
||||||
|
const createUserRole = jest.fn();
|
||||||
|
const createTeamRole = jest.fn();
|
||||||
|
const api = { readUsers, readTeams, createUserRole, createTeamRole };
|
||||||
|
const roles = {
|
||||||
|
admin_role: {
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
id: 1,
|
||||||
|
name: 'Admin'
|
||||||
|
},
|
||||||
|
execute_role: {
|
||||||
|
description: 'May run any executable resources in the organization',
|
||||||
|
id: 2,
|
||||||
|
name: 'Execute'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('handleRoleCheckboxClick properly updates state', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
wrapper.setState({
|
||||||
|
selectedRoleRows: [
|
||||||
|
{
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
name: 'Admin',
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
wrapper.instance().handleRoleCheckboxClick({
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
name: 'Admin',
|
||||||
|
id: 1
|
||||||
|
});
|
||||||
|
expect(wrapper.state('selectedRoleRows')).toEqual([]);
|
||||||
|
wrapper.instance().handleRoleCheckboxClick({
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
name: 'Admin',
|
||||||
|
id: 1
|
||||||
|
});
|
||||||
|
expect(wrapper.state('selectedRoleRows')).toEqual([{
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
name: 'Admin',
|
||||||
|
id: 1
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
test('handleResourceCheckboxClick properly updates state', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
wrapper.setState({
|
||||||
|
selectedResourceRows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
wrapper.instance().handleResourceCheckboxClick({
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
});
|
||||||
|
expect(wrapper.state('selectedResourceRows')).toEqual([]);
|
||||||
|
wrapper.instance().handleResourceCheckboxClick({
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
});
|
||||||
|
expect(wrapper.state('selectedResourceRows')).toEqual([{
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
test('clicking user/team cards updates state', () => {
|
||||||
|
const spy = jest.spyOn(AddResourceRole.prototype, 'handleResourceSelect');
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
api={api}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
const selectableCardWrapper = wrapper.find('SelectableCard');
|
||||||
|
expect(selectableCardWrapper.length).toBe(2);
|
||||||
|
selectableCardWrapper.first().simulate('click');
|
||||||
|
expect(spy).toHaveBeenCalledWith('users');
|
||||||
|
expect(wrapper.state('selectedResource')).toBe('users');
|
||||||
|
selectableCardWrapper.at(1).simulate('click');
|
||||||
|
expect(spy).toHaveBeenCalledWith('teams');
|
||||||
|
expect(wrapper.state('selectedResource')).toBe('teams');
|
||||||
|
});
|
||||||
|
test('readUsers and readTeams call out to corresponding api functions', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
wrapper.instance().readUsers({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(readUsers).toHaveBeenCalledWith({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
wrapper.instance().readTeams({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
expect(readTeams).toHaveBeenCalledWith({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('handleResourceSelect clears out selected lists and sets selectedResource', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={jest.fn()}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
wrapper.setState({
|
||||||
|
selectedResource: 'teams',
|
||||||
|
selectedResourceRows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectedRoleRows: [
|
||||||
|
{
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
id: 1,
|
||||||
|
name: 'Admin'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
wrapper.instance().handleResourceSelect('users');
|
||||||
|
expect(wrapper.state()).toEqual({
|
||||||
|
selectedResource: 'users',
|
||||||
|
selectedResourceRows: [],
|
||||||
|
selectedRoleRows: []
|
||||||
|
});
|
||||||
|
wrapper.instance().handleResourceSelect('teams');
|
||||||
|
expect(wrapper.state()).toEqual({
|
||||||
|
selectedResource: 'teams',
|
||||||
|
selectedResourceRows: [],
|
||||||
|
selectedRoleRows: []
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('handleWizardSave makes correct api calls, calls onSave when done', async () => {
|
||||||
|
const handleSave = jest.fn();
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<AddResourceRole
|
||||||
|
api={api}
|
||||||
|
onClose={jest.fn()}
|
||||||
|
onSave={handleSave}
|
||||||
|
roles={roles}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('AddResourceRole');
|
||||||
|
wrapper.setState({
|
||||||
|
selectedResource: 'users',
|
||||||
|
selectedResourceRows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'foobar'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectedRoleRows: [
|
||||||
|
{
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
id: 1,
|
||||||
|
name: 'Admin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'May run any executable resources in the organization',
|
||||||
|
id: 2,
|
||||||
|
name: 'Execute'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await wrapper.instance().handleWizardSave();
|
||||||
|
expect(createUserRole).toHaveBeenCalledTimes(2);
|
||||||
|
expect(handleSave).toHaveBeenCalled();
|
||||||
|
wrapper.setState({
|
||||||
|
selectedResource: 'teams',
|
||||||
|
selectedResourceRows: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'foobar'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
selectedRoleRows: [
|
||||||
|
{
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
id: 1,
|
||||||
|
name: 'Admin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'May run any executable resources in the organization',
|
||||||
|
id: 2,
|
||||||
|
name: 'Execute'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
await wrapper.instance().handleWizardSave();
|
||||||
|
expect(createTeamRole).toHaveBeenCalledTimes(2);
|
||||||
|
expect(handleSave).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
17
__tests__/components/CheckboxCard.test.jsx
Normal file
17
__tests__/components/CheckboxCard.test.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import CheckboxCard from '../../src/components/AddRole/CheckboxCard';
|
||||||
|
|
||||||
|
describe('<CheckboxCard />', () => {
|
||||||
|
let wrapper;
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
wrapper = mount(
|
||||||
|
<CheckboxCard
|
||||||
|
name="Foobar"
|
||||||
|
itemId={5}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
170
__tests__/components/SelectResourceStep.test.jsx
Normal file
170
__tests__/components/SelectResourceStep.test.jsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
|
import SelectResourceStep from '../../src/components/AddRole/SelectResourceStep';
|
||||||
|
|
||||||
|
describe('<SelectResourceStep />', () => {
|
||||||
|
const columns = [
|
||||||
|
{ name: 'Username', key: 'username', isSortable: true }
|
||||||
|
];
|
||||||
|
afterEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
defaultSearchParams={{
|
||||||
|
is_superuser: false
|
||||||
|
}}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={jest.fn()}
|
||||||
|
onSearch={jest.fn()}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('fetches resources on mount', async () => {
|
||||||
|
const handleSearch = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 2,
|
||||||
|
results: [
|
||||||
|
{ id: 1, username: 'foo' },
|
||||||
|
{ id: 2, username: 'bar' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
defaultSearchParams={{
|
||||||
|
is_superuser: false
|
||||||
|
}}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={jest.fn()}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
expect(handleSearch).toHaveBeenCalledWith({
|
||||||
|
is_superuser: false,
|
||||||
|
order_by: 'username',
|
||||||
|
page: 1,
|
||||||
|
page_size: 5
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('readResourceList properly adds rows to state', async () => {
|
||||||
|
const selectedResourceRows = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
username: 'foo'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const handleSearch = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
count: 2,
|
||||||
|
results: [
|
||||||
|
{ id: 1, username: 'foo' },
|
||||||
|
{ id: 2, username: 'bar' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const wrapper = await mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
defaultSearchParams={{
|
||||||
|
is_superuser: false
|
||||||
|
}}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={jest.fn()}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('SelectResourceStep');
|
||||||
|
await wrapper.instance().readResourceList({
|
||||||
|
page: 1,
|
||||||
|
order_by: '-username'
|
||||||
|
});
|
||||||
|
expect(handleSearch).toHaveBeenCalledWith({
|
||||||
|
is_superuser: false,
|
||||||
|
order_by: '-username',
|
||||||
|
page: 1
|
||||||
|
});
|
||||||
|
expect(wrapper.state('resources')).toEqual([
|
||||||
|
{ id: 1, username: 'foo' },
|
||||||
|
{ id: 2, username: 'bar' }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('handleSetPage calls readResourceList with correct params', () => {
|
||||||
|
const spy = jest.spyOn(SelectResourceStep.prototype, 'readResourceList');
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={jest.fn()}
|
||||||
|
onSearch={jest.fn()}
|
||||||
|
selectedResourceRows={[]}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).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 = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={jest.fn()}
|
||||||
|
onSearch={jest.fn()}
|
||||||
|
selectedResourceRows={[]}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).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 wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={columns}
|
||||||
|
displayKey="username"
|
||||||
|
onRowClick={handleRowClick}
|
||||||
|
onSearch={jest.fn()}
|
||||||
|
selectedResourceRows={[]}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
const selectResourceStepWrapper = wrapper.find('SelectResourceStep');
|
||||||
|
selectResourceStepWrapper.setState({
|
||||||
|
resources: [
|
||||||
|
{ id: 1, username: 'foo' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
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' });
|
||||||
|
});
|
||||||
|
});
|
||||||
63
__tests__/components/SelectRoleStep.test.jsx
Normal file
63
__tests__/components/SelectRoleStep.test.jsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import SelectRoleStep from '../../src/components/AddRole/SelectRoleStep';
|
||||||
|
|
||||||
|
describe('<SelectRoleStep />', () => {
|
||||||
|
let wrapper;
|
||||||
|
const roles = {
|
||||||
|
project_admin_role: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Project Admin',
|
||||||
|
description: 'Can manage all projects of the organization'
|
||||||
|
},
|
||||||
|
execute_role: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Execute',
|
||||||
|
description: 'May run any executable resources in the organization'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const selectedRoles = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Project Admin',
|
||||||
|
description: 'Can manage all projects of the organization'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const selectedResourceRows = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'foo'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
test('initially renders without crashing', () => {
|
||||||
|
wrapper = mount(
|
||||||
|
<SelectRoleStep
|
||||||
|
roles={roles}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
selectedRoleRows={selectedRoles}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('clicking role fires onRolesClick callback', () => {
|
||||||
|
const onRolesClick = jest.fn();
|
||||||
|
wrapper = mount(
|
||||||
|
<SelectRoleStep
|
||||||
|
onRolesClick={onRolesClick}
|
||||||
|
roles={roles}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
selectedRoleRows={selectedRoles}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const CheckboxCards = wrapper.find('CheckboxCard');
|
||||||
|
expect(CheckboxCards.length).toBe(2);
|
||||||
|
CheckboxCards.first().prop('onSelect')();
|
||||||
|
expect(onRolesClick).toBeCalledWith({
|
||||||
|
id: 1,
|
||||||
|
name: 'Project Admin',
|
||||||
|
description: 'Can manage all projects of the organization'
|
||||||
|
});
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
__tests__/components/SelectableCard.test.jsx
Normal file
27
__tests__/components/SelectableCard.test.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
import SelectableCard from '../../src/components/AddRole/SelectableCard';
|
||||||
|
|
||||||
|
describe('<SelectableCard />', () => {
|
||||||
|
let wrapper;
|
||||||
|
const onClick = jest.fn();
|
||||||
|
test('initially renders without crashing when not selected', () => {
|
||||||
|
wrapper = mount(
|
||||||
|
<SelectableCard
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
test('initially renders without crashing when selected', () => {
|
||||||
|
wrapper = mount(
|
||||||
|
<SelectableCard
|
||||||
|
isSelected
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -2219,7 +2219,7 @@
|
|||||||
},
|
},
|
||||||
"ansi-colors": {
|
"ansi-colors": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz",
|
||||||
"integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
|
"integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-wrap": "^0.1.0"
|
"ansi-wrap": "^0.1.0"
|
||||||
@@ -3239,12 +3239,12 @@
|
|||||||
},
|
},
|
||||||
"babel-plugin-syntax-class-properties": {
|
"babel-plugin-syntax-class-properties": {
|
||||||
"version": "6.13.0",
|
"version": "6.13.0",
|
||||||
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
|
||||||
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
|
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94="
|
||||||
},
|
},
|
||||||
"babel-plugin-syntax-flow": {
|
"babel-plugin-syntax-flow": {
|
||||||
"version": "6.18.0",
|
"version": "6.18.0",
|
||||||
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
||||||
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
|
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0="
|
||||||
},
|
},
|
||||||
"babel-plugin-syntax-jsx": {
|
"babel-plugin-syntax-jsx": {
|
||||||
@@ -5421,7 +5421,7 @@
|
|||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
@@ -6640,7 +6640,7 @@
|
|||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
@@ -10625,7 +10625,7 @@
|
|||||||
},
|
},
|
||||||
"kind-of": {
|
"kind-of": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
|
||||||
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ="
|
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ="
|
||||||
},
|
},
|
||||||
"kleur": {
|
"kleur": {
|
||||||
|
|||||||
18
src/api.js
18
src/api.js
@@ -5,6 +5,8 @@ const API_V2 = `${API_ROOT}v2/`;
|
|||||||
const API_CONFIG = `${API_V2}config/`;
|
const API_CONFIG = `${API_V2}config/`;
|
||||||
const API_ORGANIZATIONS = `${API_V2}organizations/`;
|
const API_ORGANIZATIONS = `${API_V2}organizations/`;
|
||||||
const API_INSTANCE_GROUPS = `${API_V2}instance_groups/`;
|
const API_INSTANCE_GROUPS = `${API_V2}instance_groups/`;
|
||||||
|
const API_USERS = `${API_V2}users/`;
|
||||||
|
const API_TEAMS = `${API_V2}teams/`;
|
||||||
|
|
||||||
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
|
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
|
||||||
|
|
||||||
@@ -140,6 +142,22 @@ class APIClient {
|
|||||||
disassociate (url, id) {
|
disassociate (url, id) {
|
||||||
return this.http.post(url, { id, disassociate: true });
|
return this.http.post(url, { id, disassociate: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readUsers (params) {
|
||||||
|
return this.http.get(API_USERS, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
readTeams (params) {
|
||||||
|
return this.http.get(API_TEAMS, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
createUserRole (userId, roleId) {
|
||||||
|
return this.http.post(`${API_USERS}${userId}/roles/`, { id: roleId });
|
||||||
|
}
|
||||||
|
|
||||||
|
createTeamRole (teamId, roleId) {
|
||||||
|
return this.http.post(`${API_TEAMS}${teamId}/roles/`, { id: roleId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default APIClient;
|
export default APIClient;
|
||||||
|
|||||||
258
src/components/AddRole/AddResourceRole.jsx
Normal file
258
src/components/AddRole/AddResourceRole.jsx
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { I18n, i18nMark } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
BackgroundImageSrc,
|
||||||
|
Wizard
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import SelectResourceStep from './SelectResourceStep';
|
||||||
|
import SelectRoleStep from './SelectRoleStep';
|
||||||
|
import SelectableCard from './SelectableCard';
|
||||||
|
|
||||||
|
class AddResourceRole extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
selectedResource: null,
|
||||||
|
selectedResourceRows: [],
|
||||||
|
selectedRoleRows: []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind(this);
|
||||||
|
this.handleResourceSelect = this.handleResourceSelect.bind(this);
|
||||||
|
this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
|
||||||
|
this.handleWizardSave = this.handleWizardSave.bind(this);
|
||||||
|
this.readTeams = this.readTeams.bind(this);
|
||||||
|
this.readUsers = this.readUsers.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResourceCheckboxClick (user) {
|
||||||
|
const { selectedResourceRows } = this.state;
|
||||||
|
|
||||||
|
const selectedIndex = selectedResourceRows
|
||||||
|
.findIndex(selectedRow => selectedRow.id === user.id);
|
||||||
|
|
||||||
|
if (selectedIndex > -1) {
|
||||||
|
selectedResourceRows.splice(selectedIndex, 1);
|
||||||
|
this.setState({ selectedResourceRows });
|
||||||
|
} else {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
selectedResourceRows: [...prevState.selectedResourceRows, user]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRoleCheckboxClick (role) {
|
||||||
|
const { selectedRoleRows } = this.state;
|
||||||
|
|
||||||
|
const selectedIndex = selectedRoleRows
|
||||||
|
.findIndex(selectedRow => selectedRow.id === role.id);
|
||||||
|
|
||||||
|
if (selectedIndex > -1) {
|
||||||
|
selectedRoleRows.splice(selectedIndex, 1);
|
||||||
|
this.setState({ selectedRoleRows });
|
||||||
|
} else {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
selectedRoleRows: [...prevState.selectedRoleRows, role]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResourceSelect (resourceType) {
|
||||||
|
this.setState({
|
||||||
|
selectedResource: resourceType,
|
||||||
|
selectedResourceRows: [],
|
||||||
|
selectedRoleRows: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleWizardSave () {
|
||||||
|
const {
|
||||||
|
onSave,
|
||||||
|
api
|
||||||
|
} = this.props;
|
||||||
|
const {
|
||||||
|
selectedResourceRows,
|
||||||
|
selectedRoleRows,
|
||||||
|
selectedResource
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const roleRequests = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < selectedResourceRows.length; i++) {
|
||||||
|
for (let j = 0; j < selectedRoleRows.length; j++) {
|
||||||
|
if (selectedResource === 'users') {
|
||||||
|
roleRequests.push(
|
||||||
|
api.createUserRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
||||||
|
);
|
||||||
|
} else if (selectedResource === 'teams') {
|
||||||
|
roleRequests.push(
|
||||||
|
api.createTeamRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(roleRequests);
|
||||||
|
onSave();
|
||||||
|
} catch (err) {
|
||||||
|
// TODO: handle this error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async readUsers (queryParams) {
|
||||||
|
const { api } = this.props;
|
||||||
|
return api.readUsers(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readTeams (queryParams) {
|
||||||
|
const { api } = this.props;
|
||||||
|
return api.readTeams(queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
selectedResource,
|
||||||
|
selectedResourceRows,
|
||||||
|
selectedRoleRows
|
||||||
|
} = this.state;
|
||||||
|
const {
|
||||||
|
onClose,
|
||||||
|
roles
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const images = {
|
||||||
|
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
|
||||||
|
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
|
||||||
|
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
|
||||||
|
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
|
||||||
|
[BackgroundImageSrc.lg]: '/assets/images/pfbg_2000.jpg',
|
||||||
|
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg#image_overlay'
|
||||||
|
};
|
||||||
|
|
||||||
|
const userColumns = [
|
||||||
|
{ name: i18nMark('Username'), key: 'username', isSortable: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
const teamColumns = [
|
||||||
|
{ name: i18nMark('Name'), key: 'name', isSortable: true }
|
||||||
|
];
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
name: i18nMark('Select Users Or Teams'),
|
||||||
|
component: (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<SelectableCard
|
||||||
|
isSelected={selectedResource === 'users'}
|
||||||
|
label={i18n._(t`Users`)}
|
||||||
|
onClick={() => this.handleResourceSelect('users')}
|
||||||
|
/>
|
||||||
|
<SelectableCard
|
||||||
|
isSelected={selectedResource === 'teams'}
|
||||||
|
label={i18n._(t`Teams`)}
|
||||||
|
onClick={() => this.handleResourceSelect('teams')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
),
|
||||||
|
enableNext: selectedResource !== null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18nMark('Select items from list'),
|
||||||
|
component: (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<Fragment>
|
||||||
|
{selectedResource === 'users' && (
|
||||||
|
<SelectResourceStep
|
||||||
|
columns={userColumns}
|
||||||
|
defaultSearchParams={{
|
||||||
|
is_superuser: false
|
||||||
|
}}
|
||||||
|
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 Users`)}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
sortedColumnKey="username"
|
||||||
|
title={i18n._(t`Users`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{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 Teams`)}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
title={i18n._(t`Teams`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
),
|
||||||
|
enableNext: selectedResourceRows.length > 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18nMark('Apply roles'),
|
||||||
|
component: (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<SelectRoleStep
|
||||||
|
onRolesClick={this.handleRoleCheckboxClick}
|
||||||
|
roles={roles}
|
||||||
|
selectedListKey={selectedResource === 'users' ? 'username' : 'name'}
|
||||||
|
selectedListLabel={selectedResource === 'users' ? i18n._(t`Selected Users`) : i18n._(t`Selected Teams`)}
|
||||||
|
selectedResourceRows={selectedResourceRows}
|
||||||
|
selectedRoleRows={selectedRoleRows}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
),
|
||||||
|
enableNext: selectedRoleRows.length > 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<Wizard
|
||||||
|
backgroundImgSrc={images}
|
||||||
|
footerRightAlign
|
||||||
|
isOpen
|
||||||
|
lastStepButtonText={i18n._(t`Save`)}
|
||||||
|
onClose={onClose}
|
||||||
|
onSave={this.handleWizardSave}
|
||||||
|
steps={steps}
|
||||||
|
title={i18n._(t`Add Roles`)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddResourceRole.propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
onSave: PropTypes.func.isRequired,
|
||||||
|
roles: PropTypes.shape()
|
||||||
|
};
|
||||||
|
|
||||||
|
AddResourceRole.defaultProps = {
|
||||||
|
roles: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddResourceRole;
|
||||||
50
src/components/AddRole/CheckboxCard.jsx
Normal file
50
src/components/AddRole/CheckboxCard.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
Checkbox
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
class CheckboxCard extends Component {
|
||||||
|
render () {
|
||||||
|
const { name, description, isSelected, onSelect, itemId } = this.props;
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
border: '1px solid var(--pf-global--BorderColor)',
|
||||||
|
borderRadius: 'var(--pf-global--BorderRadius--sm)',
|
||||||
|
padding: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={isSelected}
|
||||||
|
onChange={onSelect}
|
||||||
|
aria-label={name}
|
||||||
|
id={`checkbox-card-${itemId}`}
|
||||||
|
label={(
|
||||||
|
<Fragment>
|
||||||
|
<div style={{ fontWeight: 'bold' }}>{name}</div>
|
||||||
|
<div>{description}</div>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
value={itemId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckboxCard.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
description: PropTypes.string,
|
||||||
|
isSelected: PropTypes.bool,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
itemId: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
CheckboxCard.defaultProps = {
|
||||||
|
description: '',
|
||||||
|
isSelected: false,
|
||||||
|
onSelect: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CheckboxCard;
|
||||||
216
src/components/AddRole/SelectResourceStep.jsx
Normal file
216
src/components/AddRole/SelectResourceStep.jsx
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { i18nMark } from '@lingui/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EmptyState,
|
||||||
|
EmptyStateBody,
|
||||||
|
EmptyStateIcon,
|
||||||
|
Title,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import { CubesIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
|
import CheckboxListItem from '../ListItem';
|
||||||
|
import DataListToolbar from '../DataListToolbar';
|
||||||
|
import Pagination from '../Pagination';
|
||||||
|
import SelectedList from '../SelectedList';
|
||||||
|
|
||||||
|
class SelectResourceStep extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const { sortedColumnKey } = this.props;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { page_size, page, sortedColumnKey } = this.state;
|
||||||
|
|
||||||
|
this.readResourceList({ page_size, page, order_by: sortedColumnKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, defaultSearchParams } = 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 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await onSearch(Object.assign(queryParams, defaultSearchParams));
|
||||||
|
const { count, results } = data;
|
||||||
|
|
||||||
|
const stateToUpdate = {
|
||||||
|
count,
|
||||||
|
page,
|
||||||
|
resources: results,
|
||||||
|
sortOrder,
|
||||||
|
sortedColumnKey
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState(stateToUpdate);
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ error: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
count,
|
||||||
|
error,
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
resources,
|
||||||
|
sortOrder,
|
||||||
|
sortedColumnKey
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
displayKey,
|
||||||
|
emptyListBody,
|
||||||
|
emptyListTitle,
|
||||||
|
onRowClick,
|
||||||
|
selectedLabel,
|
||||||
|
selectedResourceRows,
|
||||||
|
title
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Fragment>
|
||||||
|
{(resources.length === 0) ? (
|
||||||
|
<EmptyState>
|
||||||
|
<EmptyStateIcon icon={CubesIcon} />
|
||||||
|
<Title size="lg">
|
||||||
|
{emptyListTitle}
|
||||||
|
</Title>
|
||||||
|
<EmptyStateBody>
|
||||||
|
{emptyListBody}
|
||||||
|
</EmptyStateBody>
|
||||||
|
</EmptyState>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<Title size="lg">
|
||||||
|
{title}
|
||||||
|
</Title>
|
||||||
|
<DataListToolbar
|
||||||
|
columns={columns}
|
||||||
|
noLeftMargin
|
||||||
|
onSearch={this.onSearch}
|
||||||
|
handleSort={this.handleSort}
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
sortedColumnKey={sortedColumnKey}
|
||||||
|
/>
|
||||||
|
<ul className="pf-c-data-list awx-c-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)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Pagination
|
||||||
|
count={count}
|
||||||
|
onSetPage={this.handleSetPage}
|
||||||
|
page={page}
|
||||||
|
pageCount={Math.ceil(count / page_size)}
|
||||||
|
pageSizeOptions={null}
|
||||||
|
page_size={page_size}
|
||||||
|
showPageSizeOptions={false}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
{selectedResourceRows.length > 0 && (
|
||||||
|
<SelectedList
|
||||||
|
displayKey={displayKey}
|
||||||
|
label={selectedLabel}
|
||||||
|
onRemove={onRowClick}
|
||||||
|
selected={selectedResourceRows}
|
||||||
|
showOverflowAfter={5}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{ error ? <div>error</div> : '' }
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectResourceStep.propTypes = {
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
defaultSearchParams: PropTypes.shape(),
|
||||||
|
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,
|
||||||
|
title: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectResourceStep.defaultProps = {
|
||||||
|
defaultSearchParams: {},
|
||||||
|
displayKey: 'name',
|
||||||
|
emptyListBody: i18nMark('Please add items to populate this list'),
|
||||||
|
emptyListTitle: i18nMark('No Items Found'),
|
||||||
|
onRowClick: () => {},
|
||||||
|
selectedLabel: i18nMark('Selected Items'),
|
||||||
|
selectedResourceRows: [],
|
||||||
|
sortedColumnKey: 'name',
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectResourceStep;
|
||||||
69
src/components/AddRole/SelectRoleStep.jsx
Normal file
69
src/components/AddRole/SelectRoleStep.jsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { i18nMark } from '@lingui/react';
|
||||||
|
|
||||||
|
import CheckboxCard from './CheckboxCard';
|
||||||
|
import SelectedList from '../SelectedList';
|
||||||
|
|
||||||
|
class RolesStep extends React.Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
onRolesClick,
|
||||||
|
roles,
|
||||||
|
selectedListKey,
|
||||||
|
selectedListLabel,
|
||||||
|
selectedResourceRows,
|
||||||
|
selectedRoleRows
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<div>
|
||||||
|
{selectedResourceRows.length > 0 && (
|
||||||
|
<SelectedList
|
||||||
|
displayKey={selectedListKey}
|
||||||
|
isReadOnly
|
||||||
|
label={selectedListLabel}
|
||||||
|
selected={selectedResourceRows}
|
||||||
|
showOverflowAfter={5}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px 20px', marginTop: '20px' }}>
|
||||||
|
{Object.keys(roles).map(role => (
|
||||||
|
<CheckboxCard
|
||||||
|
description={roles[role].description}
|
||||||
|
itemId={roles[role].id}
|
||||||
|
isSelected={
|
||||||
|
selectedRoleRows.some(item => item.id === roles[role].id)
|
||||||
|
}
|
||||||
|
key={roles[role].id}
|
||||||
|
name={roles[role].name}
|
||||||
|
onSelect={() => onRolesClick(roles[role])}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RolesStep.propTypes = {
|
||||||
|
onRolesClick: PropTypes.func,
|
||||||
|
roles: PropTypes.objectOf(PropTypes.object).isRequired,
|
||||||
|
selectedListKey: PropTypes.string,
|
||||||
|
selectedListLabel: PropTypes.string,
|
||||||
|
selectedResourceRows: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
selectedRoleRows: PropTypes.arrayOf(PropTypes.object)
|
||||||
|
};
|
||||||
|
|
||||||
|
RolesStep.defaultProps = {
|
||||||
|
onRolesClick: () => {},
|
||||||
|
selectedListKey: 'name',
|
||||||
|
selectedListLabel: i18nMark('Selected'),
|
||||||
|
selectedResourceRows: [],
|
||||||
|
selectedRoleRows: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RolesStep;
|
||||||
39
src/components/AddRole/SelectableCard.jsx
Normal file
39
src/components/AddRole/SelectableCard.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class SelectableCard extends Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
isSelected
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={isSelected ? 'awx-selectableCard awx-selectableCard__selected' : 'awx-selectableCard'}
|
||||||
|
onClick={onClick}
|
||||||
|
onKeyPress={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="awx-selectableCard__indicator"
|
||||||
|
/>
|
||||||
|
<div className="awx-selectableCard__label">{label}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectableCard.propTypes = {
|
||||||
|
label: PropTypes.string,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
isSelected: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectableCard.defaultProps = {
|
||||||
|
label: '',
|
||||||
|
isSelected: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectableCard;
|
||||||
28
src/components/AddRole/styles.scss
Normal file
28
src/components/AddRole/styles.scss
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
.awx-selectableCard {
|
||||||
|
min-width: 200px;
|
||||||
|
border: 1px solid var(--pf-global--BorderColor);
|
||||||
|
border-radius: var(--pf-global--BorderRadius--sm);
|
||||||
|
margin-right: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.awx-selectableCard__indicator {
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awx-selectableCard__label {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.awx-selectableCard.awx-selectableCard__selected {
|
||||||
|
border-color: var(--pf-global--active-color--100);
|
||||||
|
|
||||||
|
.awx-selectableCard__indicator {
|
||||||
|
background-color: var(--pf-global--active-color--100);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ class DataListToolbar extends React.Component {
|
|||||||
isAllSelected,
|
isAllSelected,
|
||||||
isLookup,
|
isLookup,
|
||||||
isCompact,
|
isCompact,
|
||||||
|
noLeftMargin,
|
||||||
onSort,
|
onSort,
|
||||||
onSearch,
|
onSearch,
|
||||||
onCompact,
|
onCompact,
|
||||||
@@ -54,7 +55,7 @@ class DataListToolbar extends React.Component {
|
|||||||
<div className="awx-toolbar">
|
<div className="awx-toolbar">
|
||||||
<Level>
|
<Level>
|
||||||
<LevelItem style={{ display: 'flex', flexBasis: '700px' }}>
|
<LevelItem style={{ display: 'flex', flexBasis: '700px' }}>
|
||||||
<Toolbar style={{ marginLeft: isLookup ? '0px' : '20px', flexGrow: '1' }}>
|
<Toolbar style={{ marginLeft: noLeftMargin ? '0px' : '20px', flexGrow: '1' }}>
|
||||||
{ showSelectAll && (
|
{ showSelectAll && (
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
@@ -152,6 +153,7 @@ DataListToolbar.propTypes = {
|
|||||||
addUrl: PropTypes.string,
|
addUrl: PropTypes.string,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isAllSelected: PropTypes.bool,
|
isAllSelected: PropTypes.bool,
|
||||||
|
noLeftMargin: PropTypes.bool,
|
||||||
onSearch: PropTypes.func,
|
onSearch: PropTypes.func,
|
||||||
onSelectAll: PropTypes.func,
|
onSelectAll: PropTypes.func,
|
||||||
onSort: PropTypes.func,
|
onSort: PropTypes.func,
|
||||||
@@ -178,7 +180,8 @@ DataListToolbar.defaultProps = {
|
|||||||
onCompact: null,
|
onCompact: null,
|
||||||
onExpand: null,
|
onExpand: null,
|
||||||
isCompact: false,
|
isCompact: false,
|
||||||
add: null
|
add: null,
|
||||||
|
noLeftMargin: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataListToolbar;
|
export default DataListToolbar;
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ class Lookup extends React.Component {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
onSearch={this.onSearch}
|
onSearch={this.onSearch}
|
||||||
onSort={this.onSort}
|
onSort={this.onSort}
|
||||||
isLookup
|
noLeftMargin
|
||||||
/>
|
/>
|
||||||
<ul className="pf-c-data-list awx-c-list">
|
<ul className="pf-c-data-list awx-c-list">
|
||||||
{results.map(i => (
|
{results.map(i => (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
--awx-pagination--disabled-Color: #C2C2CA;
|
--awx-pagination--disabled-Color: #C2C2CA;
|
||||||
|
|
||||||
border-top: 1px solid var(--awx-pagination--BorderColor);
|
border-top: 1px solid var(--awx-pagination--BorderColor);
|
||||||
|
border-bottom: 1px solid var(--awx-pagination--BorderColor);
|
||||||
background-color: var(--awx-pagination--BackgroundColor);
|
background-color: var(--awx-pagination--BackgroundColor);
|
||||||
height: 55px;
|
height: 55px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Chip
|
Chip
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import BasicChip from '../BasicChip/BasicChip';
|
||||||
import VerticalSeparator from '../VerticalSeparator';
|
import VerticalSeparator from '../VerticalSeparator';
|
||||||
|
|
||||||
const selectedRowStyling = {
|
const selectedRowStyling = {
|
||||||
@@ -35,7 +36,14 @@ class SelectedList extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { label, selected, showOverflowAfter, onRemove } = this.props;
|
const {
|
||||||
|
label,
|
||||||
|
selected,
|
||||||
|
showOverflowAfter,
|
||||||
|
onRemove,
|
||||||
|
displayKey,
|
||||||
|
isReadOnly
|
||||||
|
} = this.props;
|
||||||
const { showOverflow } = this.state;
|
const { showOverflow } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="awx-selectedList">
|
<div className="awx-selectedList">
|
||||||
@@ -46,16 +54,33 @@ class SelectedList extends Component {
|
|||||||
<VerticalSeparator />
|
<VerticalSeparator />
|
||||||
<div className="pf-l-split__item">
|
<div className="pf-l-split__item">
|
||||||
<div className="pf-c-chip-group">
|
<div className="pf-c-chip-group">
|
||||||
{selected
|
{isReadOnly ? (
|
||||||
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
<Fragment>
|
||||||
.map(selectedItem => (
|
{selected
|
||||||
<Chip
|
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
||||||
key={selectedItem.id}
|
.map(selectedItem => (
|
||||||
onClick={() => onRemove(selectedItem)}
|
<BasicChip
|
||||||
>
|
key={selectedItem.id}
|
||||||
{selectedItem.name}
|
text={selectedItem[displayKey]}
|
||||||
</Chip>
|
/>
|
||||||
))}
|
))
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
{selected
|
||||||
|
.slice(0, showOverflow ? selected.length : showOverflowAfter)
|
||||||
|
.map(selectedItem => (
|
||||||
|
<Chip
|
||||||
|
key={selectedItem.id}
|
||||||
|
onClick={() => onRemove(selectedItem)}
|
||||||
|
>
|
||||||
|
{selectedItem[displayKey]}
|
||||||
|
</Chip>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
{(
|
{(
|
||||||
!showOverflow
|
!showOverflow
|
||||||
&& selected.length > showOverflowAfter
|
&& selected.length > showOverflowAfter
|
||||||
@@ -76,15 +101,20 @@ class SelectedList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SelectedList.propTypes = {
|
SelectedList.propTypes = {
|
||||||
|
displayKey: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func,
|
||||||
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
selected: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
showOverflowAfter: PropTypes.number,
|
showOverflowAfter: PropTypes.number,
|
||||||
|
isReadOnly: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectedList.defaultProps = {
|
SelectedList.defaultProps = {
|
||||||
|
displayKey: 'name',
|
||||||
label: 'Selected',
|
label: 'Selected',
|
||||||
|
onRemove: () => null,
|
||||||
showOverflowAfter: 5,
|
showOverflowAfter: 5,
|
||||||
|
isReadOnly: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectedList;
|
export default SelectedList;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import './app.scss';
|
|||||||
import './components/Pagination/styles.scss';
|
import './components/Pagination/styles.scss';
|
||||||
import './components/DataListToolbar/styles.scss';
|
import './components/DataListToolbar/styles.scss';
|
||||||
import './components/SelectedList/styles.scss';
|
import './components/SelectedList/styles.scss';
|
||||||
|
import './components/AddRole/styles.scss';
|
||||||
|
|
||||||
import { Config } from './contexts/Config';
|
import { Config } from './contexts/Config';
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
TextContent, TextVariants, Chip, Button
|
TextContent, TextVariants, Chip, Button
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PlusIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
|
||||||
import { I18n, i18nMark } from '@lingui/react';
|
import { I18n, i18nMark } from '@lingui/react';
|
||||||
import { t, Trans } from '@lingui/macro';
|
import { t, Trans } from '@lingui/macro';
|
||||||
|
|
||||||
@@ -19,6 +23,7 @@ import { withNetwork } from '../../../contexts/Network';
|
|||||||
import AlertModal from '../../../components/AlertModal';
|
import AlertModal from '../../../components/AlertModal';
|
||||||
import Pagination from '../../../components/Pagination';
|
import Pagination from '../../../components/Pagination';
|
||||||
import DataListToolbar from '../../../components/DataListToolbar';
|
import DataListToolbar from '../../../components/DataListToolbar';
|
||||||
|
import AddResourceRole from '../../../components/AddRole/AddResourceRole';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
@@ -109,6 +114,7 @@ class OrganizationAccessList extends React.Component {
|
|||||||
deleteRoleId: null,
|
deleteRoleId: null,
|
||||||
deleteResourceId: null,
|
deleteResourceId: null,
|
||||||
results: [],
|
results: [],
|
||||||
|
isModalOpen: false
|
||||||
};
|
};
|
||||||
|
|
||||||
this.fetchOrgAccessList = this.fetchOrgAccessList.bind(this);
|
this.fetchOrgAccessList = this.fetchOrgAccessList.bind(this);
|
||||||
@@ -119,6 +125,8 @@ class OrganizationAccessList extends React.Component {
|
|||||||
this.handleWarning = this.handleWarning.bind(this);
|
this.handleWarning = this.handleWarning.bind(this);
|
||||||
this.hideWarning = this.hideWarning.bind(this);
|
this.hideWarning = this.hideWarning.bind(this);
|
||||||
this.confirmDelete = this.confirmDelete.bind(this);
|
this.confirmDelete = this.confirmDelete.bind(this);
|
||||||
|
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||||
|
this.handleSuccessfulRoleAdd = this.handleSuccessfulRoleAdd.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -282,6 +290,22 @@ class OrganizationAccessList extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSuccessfulRoleAdd () {
|
||||||
|
this.handleModalToggle();
|
||||||
|
const queryParams = this.getQueryParams();
|
||||||
|
try {
|
||||||
|
this.fetchOrgAccessList(queryParams);
|
||||||
|
} catch (error) {
|
||||||
|
this.setState({ error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModalToggle () {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
isModalOpen: !prevState.isModalOpen,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
hideWarning () {
|
hideWarning () {
|
||||||
this.setState({ showWarning: false });
|
this.setState({ showWarning: false });
|
||||||
}
|
}
|
||||||
@@ -303,8 +327,13 @@ class OrganizationAccessList extends React.Component {
|
|||||||
sortOrder,
|
sortOrder,
|
||||||
warningMsg,
|
warningMsg,
|
||||||
warningTitle,
|
warningTitle,
|
||||||
showWarning
|
showWarning,
|
||||||
|
isModalOpen
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
const {
|
||||||
|
api,
|
||||||
|
organization
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<I18n>
|
<I18n>
|
||||||
{({ i18n }) => (
|
{({ i18n }) => (
|
||||||
@@ -328,6 +357,25 @@ class OrganizationAccessList extends React.Component {
|
|||||||
columns={this.columns}
|
columns={this.columns}
|
||||||
onSearch={() => { }}
|
onSearch={() => { }}
|
||||||
onSort={this.onSort}
|
onSort={this.onSort}
|
||||||
|
add={(
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
aria-label={i18n._(t`Add Access Role`)}
|
||||||
|
onClick={this.handleModalToggle}
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
</Button>
|
||||||
|
{isModalOpen && (
|
||||||
|
<AddResourceRole
|
||||||
|
onClose={this.handleModalToggle}
|
||||||
|
onSave={this.handleSuccessfulRoleAdd}
|
||||||
|
api={api}
|
||||||
|
roles={organization.summary_fields.object_roles}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
{showWarning && (
|
{showWarning && (
|
||||||
<AlertModal
|
<AlertModal
|
||||||
@@ -419,7 +467,7 @@ class OrganizationAccessList extends React.Component {
|
|||||||
|
|
||||||
OrganizationAccessList.propTypes = {
|
OrganizationAccessList.propTypes = {
|
||||||
getAccessList: PropTypes.func.isRequired,
|
getAccessList: PropTypes.func.isRequired,
|
||||||
removeRole: PropTypes.func.isRequired,
|
removeRole: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OrganizationAccessList as _OrganizationAccessList };
|
export { OrganizationAccessList as _OrganizationAccessList };
|
||||||
|
|||||||
Reference in New Issue
Block a user