mirror of
https://github.com/ansible/awx.git
synced 2026-03-23 20:05:03 -02:30
Add delete button to team details
This commit is contained in:
@@ -1,17 +1,40 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import AlertModal from '@components/AlertModal';
|
||||||
import { CardBody, CardActionsRow } from '@components/Card';
|
import { CardBody, CardActionsRow } from '@components/Card';
|
||||||
|
import ContentLoading from '@components/ContentLoading';
|
||||||
|
import DeleteButton from '@components/DeleteButton';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import { formatDateString } from '@util/dates';
|
import { formatDateString } from '@util/dates';
|
||||||
|
import { TeamsAPI } from '@api';
|
||||||
|
|
||||||
function TeamDetail({ team, i18n }) {
|
function TeamDetail({ team, i18n }) {
|
||||||
const { name, description, created, modified, summary_fields } = team;
|
const { name, description, created, modified, summary_fields } = team;
|
||||||
|
const [deletionError, setDeletionError] = useState(null);
|
||||||
|
const [hasContentLoading, setHasContentLoading] = useState(false);
|
||||||
|
const history = useHistory();
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
setHasContentLoading(true);
|
||||||
|
try {
|
||||||
|
await TeamsAPI.destroy(id);
|
||||||
|
history.push(`/teams`);
|
||||||
|
} catch (error) {
|
||||||
|
setDeletionError(error);
|
||||||
|
}
|
||||||
|
setHasContentLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasContentLoading) {
|
||||||
|
return <ContentLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
@@ -36,12 +59,38 @@ function TeamDetail({ team, i18n }) {
|
|||||||
/>
|
/>
|
||||||
</DetailList>
|
</DetailList>
|
||||||
<CardActionsRow>
|
<CardActionsRow>
|
||||||
{summary_fields.user_capabilities.edit && (
|
{summary_fields.user_capabilities &&
|
||||||
<Button component={Link} to={`/teams/${id}/edit`}>
|
summary_fields.user_capabilities.edit && (
|
||||||
{i18n._(t`Edit`)}
|
<Button
|
||||||
</Button>
|
aria-label={i18n._(t`Edit`)}
|
||||||
)}
|
component={Link}
|
||||||
|
to={`/teams/${id}/edit`}
|
||||||
|
>
|
||||||
|
{i18n._(t`Edit`)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{summary_fields.user_capabilities &&
|
||||||
|
summary_fields.user_capabilities.delete && (
|
||||||
|
<DeleteButton
|
||||||
|
name={name}
|
||||||
|
modalTitle={i18n._(t`Delete Team`)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
>
|
||||||
|
{i18n._(t`Delete`)}
|
||||||
|
</DeleteButton>
|
||||||
|
)}
|
||||||
</CardActionsRow>
|
</CardActionsRow>
|
||||||
|
{deletionError && (
|
||||||
|
<AlertModal
|
||||||
|
isOpen={deletionError}
|
||||||
|
variant="danger"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={() => setDeletionError(null)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to delete team.`)}
|
||||||
|
<ErrorDetail error={deletionError} />
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import TeamDetail from './TeamDetail';
|
import TeamDetail from './TeamDetail';
|
||||||
|
import { TeamsAPI } from '@api';
|
||||||
|
|
||||||
jest.mock('@api');
|
jest.mock('@api');
|
||||||
|
|
||||||
describe('<TeamDetail />', () => {
|
describe('<TeamDetail />', () => {
|
||||||
|
let wrapper;
|
||||||
const mockTeam = {
|
const mockTeam = {
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
description: 'Bar',
|
description: 'Bar',
|
||||||
@@ -19,15 +20,25 @@ describe('<TeamDetail />', () => {
|
|||||||
},
|
},
|
||||||
user_capabilities: {
|
user_capabilities: {
|
||||||
edit: true,
|
edit: true,
|
||||||
|
delete: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
test('initially renders succesfully', () => {
|
|
||||||
mountWithContexts(<TeamDetail team={mockTeam} />);
|
beforeEach(async () => {
|
||||||
|
wrapper = mountWithContexts(<TeamDetail team={mockTeam} />);
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render Details', async done => {
|
afterEach(() => {
|
||||||
const wrapper = mountWithContexts(<TeamDetail team={mockTeam} />);
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initially renders succesfully', async () => {
|
||||||
|
await waitForElement(wrapper, 'TeamDetail');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render Details', async () => {
|
||||||
const testParams = [
|
const testParams = [
|
||||||
{ label: 'Name', value: 'Foo' },
|
{ label: 'Name', value: 'Foo' },
|
||||||
{ label: 'Description', value: 'Bar' },
|
{ label: 'Description', value: 'Bar' },
|
||||||
@@ -42,23 +53,52 @@ describe('<TeamDetail />', () => {
|
|||||||
expect(detail.find('dt').text()).toBe(label);
|
expect(detail.find('dt').text()).toBe(label);
|
||||||
expect(detail.find('dd').text()).toBe(value);
|
expect(detail.find('dd').text()).toBe(value);
|
||||||
}
|
}
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show edit button for users with edit permission', async done => {
|
test('should show edit button for users with edit permission', async () => {
|
||||||
const wrapper = mountWithContexts(<TeamDetail team={mockTeam} />);
|
const editButton = await waitForElement(
|
||||||
const editButton = await waitForElement(wrapper, 'TeamDetail Button');
|
wrapper,
|
||||||
|
'TeamDetail Button[aria-label="Edit"]'
|
||||||
|
);
|
||||||
expect(editButton.text()).toEqual('Edit');
|
expect(editButton.text()).toEqual('Edit');
|
||||||
expect(editButton.prop('to')).toBe('/teams/undefined/edit');
|
expect(editButton.prop('to')).toBe('/teams/undefined/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 readOnlyTeam = { ...mockTeam };
|
const readOnlyTeam = { ...mockTeam };
|
||||||
readOnlyTeam.summary_fields.user_capabilities.edit = false;
|
readOnlyTeam.summary_fields.user_capabilities.edit = false;
|
||||||
const wrapper = mountWithContexts(<TeamDetail team={readOnlyTeam} />);
|
wrapper = mountWithContexts(<TeamDetail team={readOnlyTeam} />);
|
||||||
await waitForElement(wrapper, 'TeamDetail');
|
await waitForElement(wrapper, 'TeamDetail');
|
||||||
expect(wrapper.find('TeamDetail Button').length).toBe(0);
|
expect(wrapper.find('TeamDetail Button[aria-label="Edit"]').length).toBe(0);
|
||||||
done();
|
});
|
||||||
|
|
||||||
|
test('expected api call is made for delete', async () => {
|
||||||
|
await waitForElement(wrapper, 'TeamDetail Button[aria-label="Delete"]');
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||||
|
});
|
||||||
|
expect(TeamsAPI.destroy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Error dialog shown for failed deletion', async () => {
|
||||||
|
TeamsAPI.destroy.mockImplementationOnce(() => Promise.reject(new Error()));
|
||||||
|
wrapper = mountWithContexts(<TeamDetail team={mockTeam} />);
|
||||||
|
await waitForElement(wrapper, 'TeamDetail 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
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user