1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-26 16:25:06 +03:00

Merge pull request #9595 from nixocio/ui_issue_9307

Add copy functionality to EE

Add copy functionality to EE.
See: #9307

Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
Reviewed-by: Tiago Góes <tiago.goes2009@gmail.com>
This commit is contained in:
softwarefactory-project-zuul[bot] 2021-03-17 20:33:56 +00:00 committed by nixocio
commit cb1ab742e8
5 changed files with 219 additions and 21 deletions

View File

@ -26,6 +26,7 @@ function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
pull,
organization,
summary_fields,
managed_by_tower: managedByTower,
} = executionEnvironment;
const {
@ -103,25 +104,27 @@ function ExecutionEnvironmentDetails({ executionEnvironment, i18n }) {
dataCy="execution-environment-modified"
/>
</DetailList>
<CardActionsRow>
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/execution_environments/${id}/edit`}
ouiaId="edit-button"
>
{i18n._(t`Edit`)}
</Button>
<DeleteButton
name={image}
modalTitle={i18n._(t`Delete Execution Environment`)}
onConfirm={deleteExecutionEnvironment}
isDisabled={isLoading}
ouiaId="delete-button"
>
{i18n._(t`Delete`)}
</DeleteButton>
</CardActionsRow>
{!managedByTower && (
<CardActionsRow>
<Button
aria-label={i18n._(t`edit`)}
component={Link}
to={`/execution_environments/${id}/edit`}
ouiaId="edit-button"
>
{i18n._(t`Edit`)}
</Button>
<DeleteButton
name={image}
modalTitle={i18n._(t`Delete Execution Environment`)}
onConfirm={deleteExecutionEnvironment}
isDisabled={isLoading}
ouiaId="delete-button"
>
{i18n._(t`Delete`)}
</DeleteButton>
</CardActionsRow>
)}
{error && (
<AlertModal

View File

@ -77,6 +77,12 @@ describe('<ExecutionEnvironmentDetails/>', () => {
expect(dates).toHaveLength(2);
expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created);
expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified);
const editButton = wrapper.find('Button[aria-label="edit"]');
expect(editButton.text()).toEqual('Edit');
expect(editButton.prop('to')).toBe('/execution_environments/17/edit');
const deleteButton = wrapper.find('Button[aria-label="Delete"]');
expect(deleteButton.text()).toEqual('Delete');
});
test('should render organization detail', async () => {
@ -135,4 +141,38 @@ describe('<ExecutionEnvironmentDetails/>', () => {
expect(ExecutionEnvironmentsAPI.destroy).toHaveBeenCalledTimes(1);
expect(history.location.pathname).toBe('/execution_environments');
});
test('should not render action buttons to ee managed by tower', async () => {
await act(async () => {
wrapper = mountWithContexts(
<ExecutionEnvironmentDetails
executionEnvironment={{
...executionEnvironment,
managed_by_tower: true,
}}
/>
);
});
wrapper.update();
expect(wrapper.find('Detail[label="Image"]').prop('value')).toEqual(
executionEnvironment.image
);
expect(wrapper.find('Detail[label="Description"]').prop('value')).toEqual(
'Foo'
);
expect(wrapper.find('Detail[label="Organization"]').prop('value')).toEqual(
'Globally Available'
);
expect(
wrapper.find('Detail[label="Credential"]').prop('value').props.children
).toEqual(executionEnvironment.summary_fields.credential.name);
const dates = wrapper.find('UserDateDetail');
expect(dates).toHaveLength(2);
expect(dates.at(0).prop('date')).toEqual(executionEnvironment.created);
expect(dates.at(1).prop('date')).toEqual(executionEnvironment.modified);
expect(wrapper.find('Button[aria-label="edit"]')).toHaveLength(0);
expect(wrapper.find('Button[aria-label="Delete"]')).toHaveLength(0);
});
});

View File

@ -195,6 +195,7 @@ function ExecutionEnvironmentList({ i18n }) {
isSelected={selected.some(
row => row.id === executionEnvironment.id
)}
fetchExecutionEnvironments={fetchExecutionEnvironments}
/>
)}
emptyStateControls={

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useCallback } from 'react';
import { string, bool, func } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@ -8,7 +8,10 @@ import { Tr, Td } from '@patternfly/react-table';
import { PencilAltIcon } from '@patternfly/react-icons';
import { ActionsTd, ActionItem } from '../../../components/PaginatedTable';
import CopyButton from '../../../components/CopyButton';
import { ExecutionEnvironment } from '../../../types';
import { ExecutionEnvironmentsAPI } from '../../../api';
import { timeOfDay } from '../../../util/dates';
function ExecutionEnvironmentListItem({
executionEnvironment,
@ -17,7 +20,29 @@ function ExecutionEnvironmentListItem({
onSelect,
i18n,
rowIndex,
fetchExecutionEnvironments,
}) {
const [isDisabled, setIsDisabled] = useState(false);
const copyExecutionEnvironment = useCallback(async () => {
await ExecutionEnvironmentsAPI.copy(executionEnvironment.id, {
name: `${executionEnvironment.name} @ ${timeOfDay()}`,
});
await fetchExecutionEnvironments();
}, [
executionEnvironment.id,
executionEnvironment.name,
fetchExecutionEnvironments,
]);
const handleCopyStart = useCallback(() => {
setIsDisabled(true);
}, []);
const handleCopyFinish = useCallback(() => {
setIsDisabled(false);
}, []);
const labelId = `check-action-${executionEnvironment.id}`;
return (
@ -65,6 +90,19 @@ function ExecutionEnvironmentListItem({
<PencilAltIcon />
</Button>
</ActionItem>
<ActionItem
visible={executionEnvironment.summary_fields.user_capabilities.copy}
tooltip={i18n._(t`Copy Execution Environment`)}
>
<CopyButton
ouiaId={`copy-ee-${executionEnvironment.id}`}
isDisabled={isDisabled}
onCopyStart={handleCopyStart}
onCopyFinish={handleCopyFinish}
copyItem={copyExecutionEnvironment}
errorMessage={i18n._(t`Failed to copy execution environment`)}
/>
</ActionItem>
</ActionsTd>
</Tr>
);

View File

@ -4,8 +4,15 @@ import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import ExecutionEnvironmentListItem from './ExecutionEnvironmentListItem';
import { ExecutionEnvironmentsAPI } from '../../../api';
jest.mock('../../../api');
describe('<ExecutionEnvironmentListItem/>', () => {
afterEach(() => {
jest.clearAllMocks();
});
let wrapper;
const executionEnvironment = {
name: 'Foo',
@ -13,7 +20,10 @@ describe('<ExecutionEnvironmentListItem/>', () => {
image: 'https://registry.com/r/image/manifest',
organization: null,
credential: null,
summary_fields: { user_capabilities: { edit: true } },
summary_fields: {
user_capabilities: { edit: true, copy: true, delete: true },
},
managed_by_tower: false,
};
test('should mount successfully', async () => {
@ -71,4 +81,110 @@ describe('<ExecutionEnvironmentListItem/>', () => {
expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy();
});
test('should call api to copy execution environment', async () => {
ExecutionEnvironmentsAPI.copy.mockResolvedValue();
wrapper = mountWithContexts(
<table>
<tbody>
<ExecutionEnvironmentListItem
executionEnvironment={executionEnvironment}
detailUrl="execution_environments/1/details"
isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
);
await act(async () =>
wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
);
expect(ExecutionEnvironmentsAPI.copy).toHaveBeenCalled();
});
test('should render proper alert modal on copy error', async () => {
ExecutionEnvironmentsAPI.copy.mockRejectedValue(new Error());
wrapper = mountWithContexts(
<table>
<tbody>
<ExecutionEnvironmentListItem
executionEnvironment={executionEnvironment}
detailUrl="execution_environments/1/details"
isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
);
await act(async () =>
wrapper.find('Button[aria-label="Copy"]').prop('onClick')()
);
wrapper.update();
expect(wrapper.find('Modal').prop('isOpen')).toBe(true);
});
test('should not render copy button', async () => {
wrapper = mountWithContexts(
<table>
<tbody>
<ExecutionEnvironmentListItem
executionEnvironment={{
...executionEnvironment,
summary_fields: { user_capabilities: { copy: false } },
}}
detailUrl="execution_environments/1/details"
isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
);
expect(wrapper.find('CopyButton').length).toBe(0);
});
test('should not render the pencil action for ee managed by tower', async () => {
await act(async () => {
wrapper = mountWithContexts(
<table>
<tbody>
<ExecutionEnvironmentListItem
executionEnvironment={{
...executionEnvironment,
summary_fields: { user_capabilities: { edit: false } },
managed_by_tower: true,
}}
detailUrl="execution_environments/1/details"
isSelected={false}
onSelect={() => {}}
/>
</tbody>
</table>
);
});
expect(
wrapper
.find('Td')
.at(1)
.text()
).toBe(executionEnvironment.name);
expect(
wrapper
.find('Td')
.at(2)
.text()
).toBe(executionEnvironment.image);
expect(
wrapper
.find('Td')
.at(3)
.text()
).toBe('Globally Available');
expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy();
});
});