mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 09:57:35 -02:30
Merge pull request #4783 from AlexSCorey/4222-JobResultsDelete
Adds delete button to job details and handle delete errors Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -1,18 +1,34 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, withRouter } 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 { CardBody, Button } from '@patternfly/react-core';
|
import { CardBody, Button } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import AlertModal from '@components/AlertModal';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
import { ChipGroup, Chip, CredentialChip } from '@components/Chip';
|
||||||
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
import { VariablesInput as _VariablesInput } from '@components/CodeMirrorInput';
|
||||||
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
import { toTitleCase } from '@util/strings';
|
import { toTitleCase } from '@util/strings';
|
||||||
import { Job } from '../../../types';
|
import { Job } from '../../../types';
|
||||||
|
import {
|
||||||
|
JobsAPI,
|
||||||
|
ProjectUpdatesAPI,
|
||||||
|
SystemJobsAPI,
|
||||||
|
WorkflowJobsAPI,
|
||||||
|
InventoriesAPI,
|
||||||
|
AdHocCommandsAPI,
|
||||||
|
} from '@api';
|
||||||
|
import { JOB_TYPE_URL_SEGMENTS } from '../../../constants';
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
const ActionButtonWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
& > :not(:first-child) {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const VariablesInput = styled(_VariablesInput)`
|
const VariablesInput = styled(_VariablesInput)`
|
||||||
@@ -29,7 +45,7 @@ const VERBOSITY = {
|
|||||||
4: '4 (Connection Debug)',
|
4: '4 (Connection Debug)',
|
||||||
};
|
};
|
||||||
|
|
||||||
function JobDetail({ job, i18n }) {
|
function JobDetail({ job, i18n, history }) {
|
||||||
const {
|
const {
|
||||||
job_template: jobTemplate,
|
job_template: jobTemplate,
|
||||||
project,
|
project,
|
||||||
@@ -38,7 +54,36 @@ function JobDetail({ job, i18n }) {
|
|||||||
credentials,
|
credentials,
|
||||||
labels,
|
labels,
|
||||||
} = job.summary_fields;
|
} = job.summary_fields;
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [errorMsg, setErrorMsg] = useState();
|
||||||
|
|
||||||
|
const deleteJob = async () => {
|
||||||
|
try {
|
||||||
|
switch (job.type) {
|
||||||
|
case 'project_update':
|
||||||
|
await ProjectUpdatesAPI.destroy(job.id);
|
||||||
|
break;
|
||||||
|
case 'system_job':
|
||||||
|
await SystemJobsAPI.destroy(job.id);
|
||||||
|
break;
|
||||||
|
case 'workflow_job':
|
||||||
|
await WorkflowJobsAPI.destroy(job.id);
|
||||||
|
break;
|
||||||
|
case 'ad_hoc_command':
|
||||||
|
await AdHocCommandsAPI.destroy(job.id);
|
||||||
|
break;
|
||||||
|
case 'inventory_update':
|
||||||
|
await InventoriesAPI.destroy(job.id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
await JobsAPI.destroy(job.id);
|
||||||
|
}
|
||||||
|
history.push('/jobs');
|
||||||
|
} catch (err) {
|
||||||
|
setErrorMsg(err);
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<DetailList>
|
<DetailList>
|
||||||
@@ -145,6 +190,13 @@ function JobDetail({ job, i18n }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ActionButtonWrapper>
|
<ActionButtonWrapper>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
aria-label={i18n._(t`Delete`)}
|
||||||
|
onClick={() => setIsDeleteModalOpen(true)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Delete`)}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
aria-label="close"
|
aria-label="close"
|
||||||
@@ -154,6 +206,45 @@ function JobDetail({ job, i18n }) {
|
|||||||
{i18n._(t`Close`)}
|
{i18n._(t`Close`)}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionButtonWrapper>
|
</ActionButtonWrapper>
|
||||||
|
{isDeleteModalOpen && (
|
||||||
|
<AlertModal
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
title={i18n._(t`Delete Job`)}
|
||||||
|
variant="danger"
|
||||||
|
onClose={() => setIsDeleteModalOpen(false)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Are you sure you want to delete:`)}
|
||||||
|
<br />
|
||||||
|
<strong>{job.name}</strong>
|
||||||
|
<ActionButtonWrapper>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Close`)}
|
||||||
|
component={Link}
|
||||||
|
to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}
|
||||||
|
>
|
||||||
|
{i18n._(t`Cancel`)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
aria-label={i18n._(t`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>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
import { sleep } from '@testUtils/testUtils';
|
||||||
import JobDetail from './JobDetail';
|
import JobDetail from './JobDetail';
|
||||||
|
import { JobsAPI, ProjectUpdatesAPI } from '@api';
|
||||||
|
|
||||||
|
jest.mock('@api');
|
||||||
|
|
||||||
describe('<JobDetail />', () => {
|
describe('<JobDetail />', () => {
|
||||||
let job;
|
let job;
|
||||||
@@ -57,4 +62,63 @@ describe('<JobDetail />', () => {
|
|||||||
job.summary_fields.credentials[0]
|
job.summary_fields.credentials[0]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test('should properly delete job', async () => {
|
||||||
|
job = {
|
||||||
|
name: 'Rage',
|
||||||
|
id: 1,
|
||||||
|
type: 'job',
|
||||||
|
summary_fields: {
|
||||||
|
job_template: { name: 'Spud' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = mountWithContexts(<JobDetail job={job} />);
|
||||||
|
wrapper
|
||||||
|
.find('button')
|
||||||
|
.at(0)
|
||||||
|
.simulate('click');
|
||||||
|
await sleep(1);
|
||||||
|
wrapper.update();
|
||||||
|
const modal = wrapper.find('Modal');
|
||||||
|
expect(modal.length).toBe(1);
|
||||||
|
modal.find('button[aria-label="Delete"]').simulate('click');
|
||||||
|
expect(JobsAPI.destroy).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
// The test below is skipped until react can be upgraded to at least 16.9.0. An upgrade to
|
||||||
|
// react - router will likely be necessary also.
|
||||||
|
test.skip('should display error modal when a job does not delete properly', async () => {
|
||||||
|
job = {
|
||||||
|
name: 'Angry',
|
||||||
|
id: 'a',
|
||||||
|
type: 'project_update',
|
||||||
|
summary_fields: {
|
||||||
|
job_template: { name: 'Peanut' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ProjectUpdatesAPI.destroy.mockRejectedValue(
|
||||||
|
new Error({
|
||||||
|
response: {
|
||||||
|
config: {
|
||||||
|
method: 'delete',
|
||||||
|
url: '/api/v2/project_updates/1',
|
||||||
|
},
|
||||||
|
data: 'An error occurred',
|
||||||
|
status: 404,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const wrapper = mountWithContexts(<JobDetail job={job} />);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('button')
|
||||||
|
.at(0)
|
||||||
|
.simulate('click');
|
||||||
|
const modal = wrapper.find('Modal');
|
||||||
|
await act(async () => {
|
||||||
|
await modal.find('Button[variant="danger"]').prop('onClick')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
const errorModal = wrapper.find('ErrorDetail');
|
||||||
|
expect(errorModal.length).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user