1
0
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:
Alex Corey 2019-09-19 11:16:26 -04:00
parent 154cda7501
commit 1ebe91cbf7
2 changed files with 125 additions and 2 deletions

View File

@ -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>
);
}

View File

@ -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);
});
});