Add delete button to user details

This commit is contained in:
Marliana Lara 2020-01-23 17:53:34 -05:00
parent 2fae523fd4
commit b906f8d757
No known key found for this signature in database
GPG Key ID: 38C73B40DFA809EE
3 changed files with 124 additions and 17 deletions

View File

@ -1,12 +1,17 @@
import React from 'react';
import { Link } from 'react-router-dom';
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import AlertModal from '@components/AlertModal';
import { Button } from '@patternfly/react-core';
import { CardBody, CardActionsRow } from '@components/Card';
import ContentLoading from '@components/ContentLoading';
import DeleteButton from '@components/DeleteButton';
import { DetailList, Detail } from '@components/DetailList';
import ErrorDetail from '@components/ErrorDetail';
import { formatDateString } from '@util/dates';
import { UsersAPI } from '@api';
function UserDetail({ user, i18n }) {
const {
@ -21,6 +26,20 @@ function UserDetail({ user, i18n }) {
is_system_auditor,
summary_fields,
} = user;
const [deletionError, setDeletionError] = useState(null);
const [hasContentLoading, setHasContentLoading] = useState(false);
const history = useHistory();
const handleDelete = async () => {
setHasContentLoading(true);
try {
await UsersAPI.destroy(id);
history.push(`/users`);
} catch (error) {
setDeletionError(error);
}
setHasContentLoading(false);
};
let user_type;
if (is_superuser) {
@ -31,6 +50,10 @@ function UserDetail({ user, i18n }) {
user_type = i18n._(t`Normal User`);
}
if (hasContentLoading) {
return <ContentLoading />;
}
return (
<CardBody>
<DetailList>
@ -52,16 +75,38 @@ function UserDetail({ user, i18n }) {
<Detail label={i18n._(t`Created`)} value={formatDateString(created)} />
</DetailList>
<CardActionsRow>
{summary_fields.user_capabilities.edit && (
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/users/${id}/edit`}
>
{i18n._(t`Edit`)}
</Button>
)}
{summary_fields.user_capabilities &&
summary_fields.user_capabilities.edit && (
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/users/${id}/edit`}
>
{i18n._(t`Edit`)}
</Button>
)}
{summary_fields.user_capabilities &&
summary_fields.user_capabilities.delete && (
<DeleteButton
name={username}
modalTitle={i18n._(t`Delete User`)}
onConfirm={handleDelete}
>
{i18n._(t`Delete`)}
</DeleteButton>
)}
</CardActionsRow>
{deletionError && (
<AlertModal
isOpen={deletionError}
variant="danger"
title={i18n._(t`Error!`)}
onClose={() => setDeletionError(null)}
>
{i18n._(t`Failed to delete user.`)}
<ErrorDetail error={deletionError} />
</AlertModal>
)}
</CardBody>
);
}

View File

@ -1,9 +1,13 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import { UsersAPI } from '@api';
import UserDetail from './UserDetail';
import mockDetails from '../data.user.json';
jest.mock('@api');
describe('<UserDetail />', () => {
test('initially renders succesfully', () => {
mountWithContexts(<UserDetail user={mockDetails} />);
@ -39,7 +43,36 @@ describe('<UserDetail />', () => {
assertDetail('Created', `10/28/2019, 3:01:07 PM`);
});
test('should show edit button for users with edit permission', async done => {
test('User Type Detail should render expected strings', async () => {
let wrapper;
wrapper = mountWithContexts(
<UserDetail
user={{
...mockDetails,
is_superuser: false,
is_system_auditor: true,
}}
/>
);
expect(wrapper.find(`Detail[label="User Type"] dd`).text()).toBe(
'System Auditor'
);
wrapper = mountWithContexts(
<UserDetail
user={{
...mockDetails,
is_superuser: false,
is_system_auditor: false,
}}
/>
);
expect(wrapper.find(`Detail[label="User Type"] dd`).text()).toBe(
'Normal User'
);
});
test('should show edit button for users with edit permission', async () => {
const wrapper = mountWithContexts(<UserDetail user={mockDetails} />);
const editButton = await waitForElement(
wrapper,
@ -47,10 +80,9 @@ describe('<UserDetail />', () => {
);
expect(editButton.text()).toEqual('Edit');
expect(editButton.prop('to')).toBe(`/users/${mockDetails.id}/edit`);
done();
});
test('should hide edit button for users without edit permission', async done => {
test('should hide edit button for users without edit permission', async () => {
const wrapper = mountWithContexts(
<UserDetail
user={{
@ -65,7 +97,6 @@ describe('<UserDetail />', () => {
);
await waitForElement(wrapper, 'UserDetail');
expect(wrapper.find('UserDetail Button[aria-label="edit"]').length).toBe(0);
done();
});
test('edit button should navigate to user edit', () => {
@ -79,4 +110,35 @@ describe('<UserDetail />', () => {
.simulate('click', { button: 0 });
expect(history.location.pathname).toEqual('/users/1/edit');
});
test('expected api call is made for delete', async () => {
const wrapper = mountWithContexts(<UserDetail user={mockDetails} />);
await waitForElement(wrapper, 'UserDetail Button[aria-label="Delete"]');
await act(async () => {
wrapper.find('DeleteButton').invoke('onConfirm')();
});
expect(UsersAPI.destroy).toHaveBeenCalledTimes(1);
});
test('Error dialog shown for failed deletion', async () => {
UsersAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error()));
const wrapper = mountWithContexts(<UserDetail user={mockDetails} />);
await waitForElement(wrapper, 'UserDetail Button[aria-label="Delete"]');
await act(async () => {
wrapper.find('DeleteButton').invoke('onConfirm')();
});
await waitForElement(
wrapper,
'Modal[title="Error!"]',
el => el.length === 1
);
await act(async () => {
wrapper.find('Modal[title="Error!"]').invoke('onClose')();
});
await waitForElement(
wrapper,
'Modal[title="Error!"]',
el => el.length === 0
);
});
});

View File

@ -18,7 +18,7 @@
"summary_fields": {
"user_capabilities": {
"edit": true,
"delete": false
"delete": true
}
},
"created": "2019-10-28T15:01:07.218634Z",