From a706eb608ab2a0fc7373172bf7984a11c40ad399 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Mon, 22 Mar 2021 19:28:28 -0400 Subject: [PATCH] Support job cancellation through details panel --- .../src/screens/Job/JobDetail/JobDetail.jsx | 129 ++++++++++++++---- 1 file changed, 100 insertions(+), 29 deletions(-) diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx index 678b4c24bc..3037711e08 100644 --- a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx +++ b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx @@ -1,5 +1,5 @@ import 'styled-components/macro'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Link, useHistory } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -25,7 +25,9 @@ import { } from '../../../components/LaunchButton'; import StatusIcon from '../../../components/StatusIcon'; import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail'; +import isJobRunning from '../../../util/jobs'; import { toTitleCase } from '../../../util/strings'; +import useRequest, { useDismissableError } from '../../../util/useRequest'; import { formatDateString } from '../../../util/dates'; import { Job } from '../../../types'; import { @@ -58,6 +60,16 @@ const VERBOSITY = { 4: '4 (Connection Debug)', }; +const getJobModel = type => { + if (type === 'ad_hoc_command') return AdHocCommandsAPI; + if (type === 'inventory_update') return InventoriesAPI; + if (type === 'project_update') return ProjectUpdatesAPI; + if (type === 'system_job') return SystemJobsAPI; + if (type === 'workflow_job') return WorkflowJobsAPI; + + return JobsAPI; +}; + function JobDetail({ job, i18n }) { const { created_by, @@ -77,6 +89,24 @@ function JobDetail({ job, i18n }) { const [errorMsg, setErrorMsg] = useState(); const history = useHistory(); + const [showCancelModal, setShowCancelModal] = useState(false); + + const { + error: cancelError, + isLoading: isCancelling, + request: cancelJob, + } = useRequest( + useCallback(async () => { + await getJobModel(job.type).cancel(job.id, job.type); + }, [job.id, job.type]), + {} + ); + + const { + error: dismissableCancelError, + dismissError: dismissCancelError, + } = useDismissableError(cancelError); + const jobTypes = { project_update: i18n._(t`Source Control Update`), inventory_update: i18n._(t`Inventory Sync`), @@ -91,25 +121,7 @@ function JobDetail({ job, i18n }) { 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); - } + await getJobModel(job.type).destroy(job.id); history.push('/jobs'); } catch (err) { setErrorMsg(err); @@ -410,16 +422,75 @@ function JobDetail({ job, i18n }) { )} ))} - {job.summary_fields.user_capabilities.delete && ( - - {i18n._(t`Delete`)} - - )} + {isJobRunning(job.status) && + job?.summary_fields?.user_capabilities?.start && ( + + )} + {!isJobRunning(job.status) && + job?.summary_fields?.user_capabilities?.delete && ( + + {i18n._(t`Delete`)} + + )} + {showCancelModal && isJobRunning(job.status) && ( + setShowCancelModal(false)} + title={i18n._(t`Cancel Job`)} + label={i18n._(t`Cancel Job`)} + actions={[ + , + , + ]} + > + {i18n._( + t`Are you sure you want to submit the request to cancel this job?` + )} + + )} + {dismissableCancelError && ( + + + + )} {errorMsg && (