Add general updates for User screen

* Add type of login used as part of UserListItem.
* Add type of login used as part of UserDetail.
* Hide password field, UserForm, in case login method is LDAP or Social.
* Make username field, UserForm, not required in case login is LDAP or
Social.

See: https://github.com/ansible/awx/issues/5685
This commit is contained in:
nixocio 2020-10-09 09:01:40 -04:00
parent e6c124962b
commit 1dd7651d49
7 changed files with 105 additions and 33 deletions

View File

@ -3,7 +3,7 @@ import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import { Button, Label } from '@patternfly/react-core';
import AlertModal from '../../../components/AlertModal';
import { CardBody, CardActionsRow } from '../../../components/Card';
import DeleteButton from '../../../components/DeleteButton';
@ -46,6 +46,13 @@ function UserDetail({ user, i18n }) {
user_type = i18n._(t`Normal User`);
}
let userAuthType;
if (user.ldap_dn) {
userAuthType = i18n._(t`LDAP`);
} else if (user.auth.length > 0) {
userAuthType = i18n._(t`SOCIAL`);
}
return (
<CardBody>
<DetailList>
@ -58,6 +65,14 @@ function UserDetail({ user, i18n }) {
<Detail label={i18n._(t`First Name`)} value={`${first_name}`} />
<Detail label={i18n._(t`Last Name`)} value={`${last_name}`} />
<Detail label={i18n._(t`User Type`)} value={`${user_type}`} />
{userAuthType && (
<Detail
label={i18n._(t`Type`)}
value={
<Label aria-label={i18n._(t`login type`)}>{userAuthType}</Label>
}
/>
)}
{last_login && (
<Detail
label={i18n._(t`Last Login`)}

View File

@ -12,7 +12,7 @@ import mockDetails from '../data.user.json';
jest.mock('../../../api');
describe('<UserDetail />', () => {
test('initially renders succesfully', () => {
test('initially renders successfully', () => {
mountWithContexts(<UserDetail user={mockDetails} />);
});
@ -22,6 +22,7 @@ describe('<UserDetail />', () => {
expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label);
expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value);
}
assertDetail('Username', mockDetails.username);
assertDetail('Email', mockDetails.email);
assertDetail('First Name', mockDetails.first_name);
@ -29,6 +30,7 @@ describe('<UserDetail />', () => {
assertDetail('User Type', 'System Administrator');
assertDetail('Last Login', `11/4/2019, 11:12:36 PM`);
assertDetail('Created', `10/28/2019, 3:01:07 PM`);
assertDetail('Type', `SOCIAL`);
});
test('User Type Detail should render expected strings', async () => {

View File

@ -10,6 +10,7 @@ import {
DataListItem,
DataListItemCells,
DataListItemRow,
Label,
Tooltip,
} from '@patternfly/react-core';
@ -31,6 +32,9 @@ function UserListItem({ user, isSelected, onSelect, detailUrl, i18n }) {
user_type = i18n._(t`Normal User`);
}
const ldapUser = user.ldap_dn;
const socialAuthUser = user.auth.length > 0;
return (
<DataListItem key={user.id} aria-labelledby={labelId} id={`${user.id}`}>
<DataListItemRow>
@ -43,9 +47,25 @@ function UserListItem({ user, isSelected, onSelect, detailUrl, i18n }) {
<DataListItemCells
dataListCells={[
<DataListCell key="username" aria-label={i18n._(t`username`)}>
<Link to={`${detailUrl}`} id={labelId}>
<b>{user.username}</b>
</Link>
<span id={labelId}>
<Link to={`${detailUrl}`} id={labelId}>
<b>{user.username}</b>
</Link>
</span>
{ldapUser && (
<span css="margin-left: 12px">
<Label aria-label={i18n._(t`ldap user`)}>
{i18n._(t`LDAP`)}
</Label>
</span>
)}
{socialAuthUser && (
<span css="margin-left: 12px">
<Label aria-label={i18n._(t`social login`)}>
{i18n._(t`SOCIAL`)}
</Label>
</span>
)}
</DataListCell>,
<DataListCell
key="first-name"

View File

@ -35,10 +35,13 @@ describe('UserListItem with full permissions', () => {
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('should display user type', () => {
test('should display user data', () => {
expect(
wrapper.find('DataListCell[aria-label="user type"]').prop('children')
).toEqual('System Administrator');
expect(
wrapper.find('Label[aria-label="social login"]').prop('children')
).toEqual('SOCIAL');
});
});

View File

@ -30,6 +30,10 @@
"is_system_auditor": false,
"ldap_dn": "",
"last_login": "2019-11-04T23:12:36.777783Z",
"external_account": null,
"auth": []
"external_account": "social",
"auth": [
{
"provider": "github-org",
"uid": "9053044"
}]
}

View File

@ -18,6 +18,10 @@ function UserFormFields({ user, i18n }) {
const [organization, setOrganization] = useState(null);
const { setFieldValue } = useFormikContext();
const ldapUser = user.ldap_dn;
const socialAuthUser = user.auth?.length > 0;
const externalAccount = user.external_account;
const userTypeOptions = [
{
value: 'normal',
@ -63,8 +67,12 @@ function UserFormFields({ user, i18n }) {
label={i18n._(t`Username`)}
name="username"
type="text"
validate={required(null, i18n)}
isRequired
validate={
!ldapUser && externalAccount === null
? required(null, i18n)
: () => undefined
}
isRequired={!ldapUser && externalAccount === null}
/>
<FormField
id="user-email"
@ -73,28 +81,32 @@ function UserFormFields({ user, i18n }) {
validate={requiredEmail(i18n)}
isRequired
/>
<PasswordField
id="user-password"
label={i18n._(t`Password`)}
name="password"
validate={
!user.id
? required(i18n._(t`This field must not be blank`), i18n)
: () => undefined
}
isRequired={!user.id}
/>
<PasswordField
id="user-confirm-password"
label={i18n._(t`Confirm Password`)}
name="confirm_password"
validate={
!user.id
? required(i18n._(t`This field must not be blank`), i18n)
: () => undefined
}
isRequired={!user.id}
/>
{!ldapUser && !(socialAuthUser && externalAccount) && (
<>
<PasswordField
id="user-password"
label={i18n._(t`Password`)}
name="password"
validate={
!user.id
? required(i18n._(t`This field must not be blank`), i18n)
: () => undefined
}
isRequired={!user.id}
/>
<PasswordField
id="user-confirm-password"
label={i18n._(t`Confirm Password`)}
name="confirm_password"
validate={
!user.id
? required(i18n._(t`This field must not be blank`), i18n)
: () => undefined
}
isRequired={!user.id}
/>
</>
)}
<FormField
id="user-first-name"
label={i18n._(t`First Name`)}

View File

@ -111,7 +111,7 @@ describe('<UserForm />', () => {
await act(async () => {
wrapper = mountWithContexts(
<UserForm
user={mockData}
user={{ ...mockData, external_account: '', auth: [] }}
handleSubmit={jest.fn()}
handleCancel={jest.fn()}
/>
@ -125,6 +125,22 @@ describe('<UserForm />', () => {
expect(passwordFields.at(1).prop('isRequired')).toBe(false);
});
test('password fields are not displayed for social/ldap login', async () => {
await act(async () => {
wrapper = mountWithContexts(
<UserForm
user={mockData}
handleSubmit={jest.fn()}
handleCancel={jest.fn()}
/>
);
});
const passwordFields = wrapper.find('PasswordField');
expect(passwordFields.length).toBe(0);
});
test('should call handleSubmit when Submit button is clicked', async () => {
const handleSubmit = jest.fn();
await act(async () => {