mirror of
https://github.com/ansible/awx.git
synced 2024-10-30 22:21:13 +03:00
Adds Teams Roles List and Disassociate functionality
This commit is contained in:
parent
196368d89b
commit
a070d57080
@ -28,6 +28,17 @@ class Teams extends Base {
|
|||||||
readRoleOptions(teamId) {
|
readRoleOptions(teamId) {
|
||||||
return this.http.options(`${this.baseUrl}${teamId}/roles/`);
|
return this.http.options(`${this.baseUrl}${teamId}/roles/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readUsersAccess(teamId, params) {
|
||||||
|
return this.http.get(`${this.baseUrl}${teamId}/access_list/`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
readUsersAccessOptions(teamId) {
|
||||||
|
return this.http.options(`${this.baseUrl}${teamId}/users/`);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Teams;
|
export default Teams;
|
||||||
|
@ -17,6 +17,7 @@ import TeamDetail from './TeamDetail';
|
|||||||
import TeamEdit from './TeamEdit';
|
import TeamEdit from './TeamEdit';
|
||||||
import { TeamsAPI } from '../../api';
|
import { TeamsAPI } from '../../api';
|
||||||
import TeamAccessList from './TeamAccess';
|
import TeamAccessList from './TeamAccess';
|
||||||
|
import TeamUsersList from './TeamUsers';
|
||||||
|
|
||||||
function Team({ i18n, setBreadcrumb }) {
|
function Team({ i18n, setBreadcrumb }) {
|
||||||
const [team, setTeam] = useState(null);
|
const [team, setTeam] = useState(null);
|
||||||
@ -51,8 +52,8 @@ function Team({ i18n, setBreadcrumb }) {
|
|||||||
id: 99,
|
id: 99,
|
||||||
},
|
},
|
||||||
{ name: i18n._(t`Details`), link: `/teams/${id}/details`, id: 0 },
|
{ name: i18n._(t`Details`), link: `/teams/${id}/details`, id: 0 },
|
||||||
{ name: i18n._(t`Users`), link: `/teams/${id}/users`, id: 1 },
|
{ name: i18n._(t`Access`), link: `/teams/${id}/access`, id: 1 },
|
||||||
{ name: i18n._(t`Access`), link: `/teams/${id}/access`, id: 2 },
|
{ name: i18n._(t`Roles`), link: `/teams/${id}/roles`, id: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
let showCardHeader = true;
|
let showCardHeader = true;
|
||||||
@ -95,12 +96,12 @@ function Team({ i18n, setBreadcrumb }) {
|
|||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
{team && (
|
{team && (
|
||||||
<Route path="/teams/:id/users">
|
<Route path="/teams/:id/access">
|
||||||
<span>Coming soon :)</span>
|
<TeamUsersList />
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
{team && (
|
{team && (
|
||||||
<Route path="/teams/:id/access">
|
<Route path="/teams/:id/roles">
|
||||||
<TeamAccessList />
|
<TeamAccessList />
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
|
102
awx/ui_next/src/screens/Team/TeamUsers/TeamUserListItem.jsx
Normal file
102
awx/ui_next/src/screens/Team/TeamUsers/TeamUserListItem.jsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'styled-components/macro';
|
||||||
|
import React from 'react';
|
||||||
|
import { string, func } from 'prop-types';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
DataListItem,
|
||||||
|
DataListItemCells,
|
||||||
|
DataListItemRow,
|
||||||
|
Label as PFLabel,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import DataListCell from '../../../components/DataListCell';
|
||||||
|
|
||||||
|
import { User } from '../../../types';
|
||||||
|
|
||||||
|
function TeamUserListItem({ user, disassociateRole, detailUrl, i18n }) {
|
||||||
|
const labelId = `check-action-${user.id}`;
|
||||||
|
const Label = styled.b`
|
||||||
|
margin-right: 20px;
|
||||||
|
`;
|
||||||
|
const hasDirectRoles = user.summary_fields.direct_access.length > 0;
|
||||||
|
const hasIndirectRoles = user.summary_fields.indirect_access.length > 0;
|
||||||
|
return (
|
||||||
|
<DataListItem key={user.id} aria-labelledby={labelId} id={`${user.id}`}>
|
||||||
|
<DataListItemRow>
|
||||||
|
<DataListItemCells
|
||||||
|
dataListCells={[
|
||||||
|
<DataListCell aria-label={i18n._(t`username`)} key="username">
|
||||||
|
<Link id={labelId} to={`${detailUrl}`}>
|
||||||
|
<b>{user.username}</b>
|
||||||
|
</Link>
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell aria-label={i18n._(t`first name`)} key="first name">
|
||||||
|
{user.first_name && (
|
||||||
|
<>
|
||||||
|
<Label>{i18n._(t`First`)}</Label>
|
||||||
|
<span>{user.first_name}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell aria-label={i18n._(t`last name`)} key="last name">
|
||||||
|
{user.last_name && (
|
||||||
|
<>
|
||||||
|
<Label>{i18n._(t`Last`)}</Label>
|
||||||
|
<span>{user.last}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell aria-label={i18n._(t`roles`)} key="role">
|
||||||
|
{hasDirectRoles && (
|
||||||
|
<>
|
||||||
|
<Label>{i18n._(t`Roles`)}</Label>
|
||||||
|
<span>
|
||||||
|
{user.summary_fields.direct_access.map(role =>
|
||||||
|
role.role.name !== 'Read' ? (
|
||||||
|
<PFLabel
|
||||||
|
aria-label={role.role.name}
|
||||||
|
key={role.role.id}
|
||||||
|
role={role.role}
|
||||||
|
onClose={() => disassociateRole(role.role)}
|
||||||
|
>
|
||||||
|
{role.role.name}
|
||||||
|
</PFLabel>
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
<DataListCell
|
||||||
|
aria-label={i18n._(t`indirect role`)}
|
||||||
|
key="indirectRole"
|
||||||
|
>
|
||||||
|
{hasIndirectRoles && (
|
||||||
|
<>
|
||||||
|
<Label>{i18n._(t`Indirect Roles`)}</Label>
|
||||||
|
<span>
|
||||||
|
{user.summary_fields.indirect_access.map(role => (
|
||||||
|
<PFLabel key={role.role.id} credential={role.role}>
|
||||||
|
{role.role.name}
|
||||||
|
</PFLabel>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DataListCell>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DataListItemRow>
|
||||||
|
</DataListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamUserListItem.propTypes = {
|
||||||
|
user: User.isRequired,
|
||||||
|
detailUrl: string.isRequired,
|
||||||
|
disassociateRole: func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(TeamUserListItem);
|
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import TeamUserListItem from './TeamUserListItem';
|
||||||
|
|
||||||
|
describe('<TeamUserListItem />', () => {
|
||||||
|
const user = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Team 1',
|
||||||
|
summary_fields: {
|
||||||
|
direct_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 40,
|
||||||
|
name: 'Member',
|
||||||
|
description: 'User is a member of the team',
|
||||||
|
resource_name: ' Team 1 Org 0',
|
||||||
|
resource_type: 'team',
|
||||||
|
related: {
|
||||||
|
team: '/api/v2/teams/1/',
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indirect_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Admin',
|
||||||
|
description: 'Can manage all aspects of the organization',
|
||||||
|
resource_name: ' Organization 0',
|
||||||
|
resource_type: 'organization',
|
||||||
|
related: {
|
||||||
|
organization: '/api/v2/organizations/1/',
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['admin_role', 'member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
username: 'Casey',
|
||||||
|
firstname: 'The',
|
||||||
|
lastname: 'Cat',
|
||||||
|
email: '',
|
||||||
|
};
|
||||||
|
test('initially renders succesfully', () => {
|
||||||
|
mountWithContexts(
|
||||||
|
<TeamUserListItem
|
||||||
|
user={user}
|
||||||
|
detailUrl="/users/1/details"
|
||||||
|
disassociateRole={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('initially render prop items', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<TeamUserListItem
|
||||||
|
user={user}
|
||||||
|
detailUrl="/users/1/details"
|
||||||
|
disassociateRole={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('DataListCell[aria-label="username"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('DataListCell[aria-label="first name"]').length).toBe(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
expect(wrapper.find('DataListCell[aria-label="last name"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('DataListCell[aria-label="roles"]').length).toBe(1);
|
||||||
|
expect(
|
||||||
|
wrapper.find('DataListCell[aria-label="indirect role"]').length
|
||||||
|
).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
198
awx/ui_next/src/screens/Team/TeamUsers/TeamUsersList.jsx
Normal file
198
awx/ui_next/src/screens/Team/TeamUsers/TeamUsersList.jsx
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import React, { useEffect, useCallback, useState } from 'react';
|
||||||
|
import { useLocation, useRouteMatch, useParams } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import { Button } from '@patternfly/react-core';
|
||||||
|
import { TeamsAPI, UsersAPI } from '../../../api';
|
||||||
|
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||||
|
import AlertModal from '../../../components/AlertModal';
|
||||||
|
import DataListToolbar from '../../../components/DataListToolbar';
|
||||||
|
import ErrorDetail from '../../../components/ErrorDetail';
|
||||||
|
import PaginatedDataList, {
|
||||||
|
ToolbarAddButton,
|
||||||
|
} from '../../../components/PaginatedDataList';
|
||||||
|
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||||
|
|
||||||
|
import TeamUserListItem from './TeamUserListItem';
|
||||||
|
|
||||||
|
const QS_CONFIG = getQSConfig('user', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
order_by: 'username',
|
||||||
|
});
|
||||||
|
|
||||||
|
function TeamUsersList({ i18n }) {
|
||||||
|
const location = useLocation();
|
||||||
|
const match = useRouteMatch();
|
||||||
|
const { id: teamId } = useParams();
|
||||||
|
const [roleToDisassociate, setRoleToDisassociate] = useState([]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
result: { users, itemCount, actions },
|
||||||
|
error: contentError,
|
||||||
|
isLoading,
|
||||||
|
request: fetchRoles,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
const [response, actionsResponse] = await Promise.all([
|
||||||
|
TeamsAPI.readUsersAccess(teamId, params),
|
||||||
|
TeamsAPI.readUsersAccessOptions(teamId),
|
||||||
|
]);
|
||||||
|
return {
|
||||||
|
users: response.data.results,
|
||||||
|
itemCount: response.data.count,
|
||||||
|
actions: actionsResponse.data.actions,
|
||||||
|
};
|
||||||
|
}, [location, teamId]),
|
||||||
|
{
|
||||||
|
users: [],
|
||||||
|
itemCount: 0,
|
||||||
|
actions: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRoles();
|
||||||
|
}, [fetchRoles]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading: isDeleteLoading,
|
||||||
|
deleteItems: disassociateRole,
|
||||||
|
deletionError,
|
||||||
|
clearDeletionError,
|
||||||
|
} = useDeleteItems(
|
||||||
|
useCallback(async () => {
|
||||||
|
UsersAPI.disassociateRole(
|
||||||
|
roleToDisassociate[0].id,
|
||||||
|
roleToDisassociate[1].id
|
||||||
|
);
|
||||||
|
}, [roleToDisassociate]),
|
||||||
|
{
|
||||||
|
qsConfig: QS_CONFIG,
|
||||||
|
fetchItems: fetchRoles,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRoleDisassociation = async () => {
|
||||||
|
await disassociateRole();
|
||||||
|
setRoleToDisassociate(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasContentLoading = isDeleteLoading || isLoading;
|
||||||
|
const canAdd = actions && actions.POST;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PaginatedDataList
|
||||||
|
contentError={contentError}
|
||||||
|
hasContentLoading={hasContentLoading}
|
||||||
|
items={users}
|
||||||
|
itemCount={itemCount}
|
||||||
|
pluralizedItemName={i18n._(t`Users`)}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
toolbarSearchColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`User Name`),
|
||||||
|
key: 'username',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`First Name`),
|
||||||
|
key: 'first_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Last Name`),
|
||||||
|
key: 'last_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Email`),
|
||||||
|
key: 'email',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
toolbarSortColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`User Name`),
|
||||||
|
key: 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`First Name`),
|
||||||
|
key: 'first_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Last Name`),
|
||||||
|
key: 'last_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Email`),
|
||||||
|
key: 'email',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderToolbar={props => (
|
||||||
|
<DataListToolbar
|
||||||
|
{...props}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
additionalControls={[
|
||||||
|
...(canAdd
|
||||||
|
? [<ToolbarAddButton key="add" linkTo="/users/add" />]
|
||||||
|
: []),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderItem={user => (
|
||||||
|
<TeamUserListItem
|
||||||
|
key={user.id}
|
||||||
|
user={user}
|
||||||
|
detailUrl={`/users/${user.id}/details`}
|
||||||
|
disassociateRole={role => setRoleToDisassociate([user, role])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
emptyStateControls={
|
||||||
|
canAdd ? (
|
||||||
|
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{roleToDisassociate?.length > 0 && (
|
||||||
|
<AlertModal
|
||||||
|
variant="danger"
|
||||||
|
title={i18n._(t`Disassociate roles`)}
|
||||||
|
isOpen={roleToDisassociate}
|
||||||
|
onClose={() => setRoleToDisassociate(null)}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
key="disassociate"
|
||||||
|
variant="danger"
|
||||||
|
aria-label={i18n._(t`confirm disassociation`)}
|
||||||
|
onClick={() => handleRoleDisassociation()}
|
||||||
|
>
|
||||||
|
{i18n._(t`Disassociate`)}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`cancel disassociation`)}
|
||||||
|
onClick={() => setRoleToDisassociate(null)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Cancel`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>{i18n._(t`This action will disassociate the following:`)}</div>
|
||||||
|
<span>{roleToDisassociate.name}</span>
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
|
<AlertModal
|
||||||
|
isOpen={deletionError}
|
||||||
|
variant="error"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={clearDeletionError}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to disassociate one or more roles.`)}
|
||||||
|
<ErrorDetail error={deletionError} />
|
||||||
|
</AlertModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(TeamUsersList);
|
261
awx/ui_next/src/screens/Team/TeamUsers/TeamUsersList.test.jsx
Normal file
261
awx/ui_next/src/screens/Team/TeamUsers/TeamUsersList.test.jsx
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { TeamsAPI, UsersAPI } from '../../../api';
|
||||||
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import TeamUsersList from './TeamUsersList';
|
||||||
|
|
||||||
|
jest.mock('../../../api/models/Teams');
|
||||||
|
jest.mock('../../../api/models/Users');
|
||||||
|
|
||||||
|
const teamUsersList = {
|
||||||
|
data: {
|
||||||
|
count: 3,
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: 'user',
|
||||||
|
url: '',
|
||||||
|
summary_fields: {
|
||||||
|
direct_access: [],
|
||||||
|
indirect_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
created: '2020-06-19T12:55:13.138692Z',
|
||||||
|
username: 'admin',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: 'a@g.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
type: 'user',
|
||||||
|
url: '',
|
||||||
|
summary_fields: {
|
||||||
|
direct_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 40,
|
||||||
|
name: 'Member',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 41,
|
||||||
|
name: 'Read',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indirect_access: [],
|
||||||
|
},
|
||||||
|
created: '2020-06-19T13:01:44.183577Z',
|
||||||
|
username: 'jt_admin',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: 'user',
|
||||||
|
url: '',
|
||||||
|
summary_fields: {
|
||||||
|
direct_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 40,
|
||||||
|
name: 'Alex',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 41,
|
||||||
|
name: 'Read',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indirect_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Admin',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['admin_role', 'member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
created: '2020-06-19T13:01:43.674349Z',
|
||||||
|
username: 'org_admin',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: 'user',
|
||||||
|
url: '',
|
||||||
|
summary_fields: {
|
||||||
|
direct_access: [
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 40,
|
||||||
|
name: 'Savannah',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: {
|
||||||
|
id: 41,
|
||||||
|
name: 'Read',
|
||||||
|
user_capabilities: {
|
||||||
|
unattach: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
descendant_roles: ['member_role', 'read_role'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indirect_access: [],
|
||||||
|
},
|
||||||
|
created: '2020-06-19T13:01:43.868499Z',
|
||||||
|
username: 'org_member',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<TeamUsersList />', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TeamsAPI.readUsersAccess = jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: teamUsersList.data,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
TeamsAPI.readUsersAccessOptions = jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.unmount();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should load and render users', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<TeamUsersList />);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
expect(wrapper.find('TeamUserListItem')).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should disassociate role', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<TeamUsersList />);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Label[aria-label="Member"]').prop('onClose')({
|
||||||
|
id: 1,
|
||||||
|
name: 'Member',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AlertModal[title="Disassociate roles"]').length).toBe(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper
|
||||||
|
.find('Button[aria-label="confirm disassociation"]')
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(UsersAPI.disassociateRole).toHaveBeenCalledTimes(1);
|
||||||
|
expect(TeamsAPI.readUsersAccess).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show disassociation error', async () => {
|
||||||
|
UsersAPI.disassociateRole.mockResolvedValue(
|
||||||
|
new Error({
|
||||||
|
response: {
|
||||||
|
config: {
|
||||||
|
method: 'post',
|
||||||
|
url: '/api/v2/users/1',
|
||||||
|
},
|
||||||
|
data: 'An error occurred',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<TeamUsersList />);
|
||||||
|
});
|
||||||
|
waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Label[aria-label="Member"]').prop('onClose')({
|
||||||
|
id: 1,
|
||||||
|
name: 'Member',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('AlertModal[title="Disassociate roles"]').length).toBe(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper
|
||||||
|
.find('Button[aria-label="confirm disassociation"]')
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(UsersAPI.disassociateRole).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const modal = wrapper.find('Modal');
|
||||||
|
expect(modal).toHaveLength(1);
|
||||||
|
expect(modal.prop('title')).toEqual('Error!');
|
||||||
|
});
|
||||||
|
});
|
4
awx/ui_next/src/screens/Team/TeamUsers/index.js
Normal file
4
awx/ui_next/src/screens/Team/TeamUsers/index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
from './TeamUsersList'
|
Loading…
Reference in New Issue
Block a user