mirror of
https://github.com/ansible/awx.git
synced 2024-10-30 22:21:13 +03:00
Adds delete button to job details and handle delete errors
After successful deletion of Job the user is nativated back to Jobs List. If the job is not successfully deleted a alert modal pops up and on close the user remains on Job Details page.
This commit is contained in:
parent
154cda7501
commit
1ebe91cbf7
@ -1,18 +1,27 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { CardBody, Button } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import AlertModal from '@components/AlertModal';
|
||||
import { DetailList, Detail } from '@components/DetailList';
|
||||
import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
||||
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
||||
import ErrorDetail from '@components/ErrorDetail';
|
||||
import { toTitleCase } from '@util/strings';
|
||||
import { Job } from '../../../types';
|
||||
import { JobsAPI, ProjectUpdatesAPI } from '@api';
|
||||
import { JOB_TYPE_URL_SEGMENTS } from '../../../constants';
|
||||
|
||||
const ActionButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
& > :not(:first-child) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const VariablesInput = styled(_VariablesInput)`
|
||||
@ -29,7 +38,7 @@ const VERBOSITY = {
|
||||
4: '4 (Connection Debug)',
|
||||
};
|
||||
|
||||
function JobDetail({ job, i18n }) {
|
||||
function JobDetail({ job, i18n, history }) {
|
||||
const {
|
||||
job_template: jobTemplate,
|
||||
project,
|
||||
@ -38,7 +47,22 @@ function JobDetail({ job, i18n }) {
|
||||
credentials,
|
||||
labels,
|
||||
} = job.summary_fields;
|
||||
const [isDeleteModalOpen, setDeleteModal] = useState(false);
|
||||
const [errorMsg, setErrorMsg] = useState();
|
||||
|
||||
const deleteJob = async () => {
|
||||
try {
|
||||
if (job.type === 'job') {
|
||||
await JobsAPI.destroy(job.id);
|
||||
} else {
|
||||
await ProjectUpdatesAPI.destroy(job.id);
|
||||
}
|
||||
history.push('/jobs');
|
||||
} catch (err) {
|
||||
setErrorMsg(err);
|
||||
setDeleteModal(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList>
|
||||
@ -145,6 +169,13 @@ function JobDetail({ job, i18n }) {
|
||||
/>
|
||||
)}
|
||||
<ActionButtonWrapper>
|
||||
<Button
|
||||
variant="danger"
|
||||
aria-label="delete"
|
||||
onClick={() => setDeleteModal(true)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label="close"
|
||||
@ -154,6 +185,41 @@ function JobDetail({ job, i18n }) {
|
||||
{i18n._(t`Close`)}
|
||||
</Button>
|
||||
</ActionButtonWrapper>
|
||||
{isDeleteModalOpen && (
|
||||
<AlertModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title={i18n._(t`Delete Job`)}
|
||||
variant="danger"
|
||||
onClose={() => setDeleteModal(false)}
|
||||
>
|
||||
{i18n._(t`Are you sure you want to delete:`)}
|
||||
<br />
|
||||
<strong>{job.name}</strong>
|
||||
<ActionButtonWrapper>
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label="close"
|
||||
component={Link}
|
||||
to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>
|
||||
<Button variant="danger" aria-label="delete" onClick={deleteJob}>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>
|
||||
</ActionButtonWrapper>
|
||||
</AlertModal>
|
||||
)}
|
||||
{errorMsg && (
|
||||
<AlertModal
|
||||
isOpen={errorMsg}
|
||||
variant="danger"
|
||||
onClose={() => setErrorMsg()}
|
||||
title={i18n._(t`Job Delete Error`)}
|
||||
>
|
||||
<ErrorDetail error={errorMsg} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { sleep } from '@testUtils/testUtils';
|
||||
import JobDetail from './JobDetail';
|
||||
import { JobsAPI, ProjectUpdatesAPI } from '@api';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
describe('<JobDetail />', () => {
|
||||
let job;
|
||||
@ -57,4 +61,57 @@ describe('<JobDetail />', () => {
|
||||
job.summary_fields.credentials[0]
|
||||
);
|
||||
});
|
||||
test('should properly delete job', () => {
|
||||
job = {
|
||||
name: 'Rage',
|
||||
id: 1,
|
||||
type: 'job',
|
||||
summary_fields: {
|
||||
job_template: { name: 'Spud' },
|
||||
},
|
||||
};
|
||||
const wrapper = mountWithContexts(<JobDetail job={job} />);
|
||||
wrapper
|
||||
.find('button')
|
||||
.at(0)
|
||||
.invoke('onClick')();
|
||||
const modal = wrapper.find('Modal');
|
||||
expect(modal.length).toBe(1);
|
||||
modal.find('button[aria-label="delete"]').invoke('onClick')();
|
||||
expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should display error modal when a job does not delete properly', async () => {
|
||||
job = {
|
||||
name: 'Angry',
|
||||
id: 'a',
|
||||
type: 'project_updates',
|
||||
summary_fields: {
|
||||
job_template: { name: 'Peanut' },
|
||||
},
|
||||
};
|
||||
const wrapper = mountWithContexts(<JobDetail job={job} />);
|
||||
wrapper
|
||||
.find('button')
|
||||
.at(0)
|
||||
.invoke('onClick')();
|
||||
const modal = wrapper.find('Modal');
|
||||
ProjectUpdatesAPI.destroy.mockRejectedValue(
|
||||
new Error({
|
||||
response: {
|
||||
config: {
|
||||
method: 'delete',
|
||||
url: '/api/v2/project_updates/1',
|
||||
},
|
||||
data: 'An error occurred',
|
||||
status: 404,
|
||||
},
|
||||
})
|
||||
);
|
||||
modal.find('button[aria-label="delete"]').invoke('onClick')();
|
||||
await sleep(1);
|
||||
wrapper.update();
|
||||
const errorModal = wrapper.find('ErrorDetail__Expandable');
|
||||
expect(errorModal.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user