From 6c06b0432b289b08d1e4954d274f26b61eded52c Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Fri, 7 May 2021 16:05:46 -0400 Subject: [PATCH] adds job cancel button to job detail page and to job list --- .../JobCancelButton/JobCancelButton.jsx | 5 +- .../src/components/JobList/JobList.jsx | 5 + .../src/components/JobList/JobListItem.jsx | 16 + awx/ui_next/src/locales/en/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/es/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/fr/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/ja/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/nl/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/zh/messages.po | 287 +++++++++--------- awx/ui_next/src/locales/zu/messages.po | 287 +++++++++--------- .../src/screens/Job/JobDetail/JobDetail.jsx | 70 +---- .../screens/Job/JobDetail/JobDetail.test.jsx | 150 ++++++++- .../Job/JobOutput/shared/OutputToolbar.jsx | 91 +++--- .../Project/ProjectDetail/ProjectDetail.jsx | 1 + 14 files changed, 1200 insertions(+), 1147 deletions(-) diff --git a/awx/ui_next/src/components/JobCancelButton/JobCancelButton.jsx b/awx/ui_next/src/components/JobCancelButton/JobCancelButton.jsx index dc225ab001..e2a7cac9c7 100644 --- a/awx/ui_next/src/components/JobCancelButton/JobCancelButton.jsx +++ b/awx/ui_next/src/components/JobCancelButton/JobCancelButton.jsx @@ -13,6 +13,7 @@ function JobCancelButton({ title, showIconButton, errorMessage, + buttonText, }) { const [isOpen, setIsOpen] = useState(false); const { error: cancelError, request: cancelJob } = useRequest( @@ -44,7 +45,9 @@ function JobCancelButton({ variant="secondary" ouiaId="cancel job" onClick={() => setIsOpen(true)} - >{t`Cancel Sync`} + > + {buttonText || t`Cancel Job`} + )} {isOpen && ( diff --git a/awx/ui_next/src/components/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx index 87641d67f6..eaaa26095c 100644 --- a/awx/ui_next/src/components/JobList/JobList.jsx +++ b/awx/ui_next/src/components/JobList/JobList.jsx @@ -12,6 +12,8 @@ import useRequest, { useDeleteItems, useDismissableError, } from '../../util/useRequest'; +import { useConfig } from '../../contexts/Config'; + import { isJobRunning, getJobModel } from '../../util/jobs'; import { getQSConfig, parseQueryString } from '../../util/qs'; import JobListItem from './JobListItem'; @@ -32,6 +34,8 @@ function JobList({ defaultParams, showTypeColumn = false }) { ['id', 'page', 'page_size'] ); + const { me } = useConfig(); + const [selected, setSelected] = useState([]); const location = useLocation(); const { @@ -261,6 +265,7 @@ function JobList({ defaultParams, showTypeColumn = false }) { handleSelect(job)} isSelected={selected.some(row => row.id === job.id)} diff --git a/awx/ui_next/src/components/JobList/JobListItem.jsx b/awx/ui_next/src/components/JobList/JobListItem.jsx index ad1215f8c6..a9f7304abb 100644 --- a/awx/ui_next/src/components/JobList/JobListItem.jsx +++ b/awx/ui_next/src/components/JobList/JobListItem.jsx @@ -15,6 +15,7 @@ import CredentialChip from '../CredentialChip'; import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail'; import { formatDateString } from '../../util/dates'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; +import JobCancelButton from '../JobCancelButton'; const Dash = styled.span``; function JobListItem({ @@ -23,6 +24,7 @@ function JobListItem({ isSelected, onSelect, showTypeColumn = false, + isSuperUser = false, }) { const labelId = `check-action-${job.id}`; const [isExpanded, setIsExpanded] = useState(false); @@ -83,6 +85,20 @@ function JobListItem({ {job.finished ? formatDateString(job.finished) : ''} + + + { - await getJobModel(job.type).cancel(job.id, job.type); - }, [job.id, job.type]), - {} - ); - - const { - error: dismissableCancelError, - dismissError: dismissCancelError, - } = useDismissableError(cancelError); - const jobTypes = { project_update: t`Source Control Update`, inventory_update: t`Inventory Sync`, @@ -387,7 +370,9 @@ function JobDetail({ job }) { ))} {isJobRunning(job.status) && - job?.summary_fields?.user_capabilities?.start && ( + (job.type === 'system_job' + ? me.is_superuser + : job?.summary_fields?.user_capabilities?.start) && ( )} - {showCancelModal && isJobRunning(job.status) && ( - setShowCancelModal(false)} - title={t`Cancel Job`} - label={t`Cancel Job`} - actions={[ - , - , - ]} - > - {t`Are you sure you want to submit the request to cancel this job?`} - - )} - {dismissableCancelError && ( - - - - )} {errorMsg && ( ', () => { expect(wrapper.find(`Detail[label="${label}"] dt`).text()).toBe(label); expect(wrapper.find(`Detail[label="${label}"] dd`).text()).toBe(value); } + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); test('should display details', () => { wrapper = mountWithContexts( @@ -144,18 +149,15 @@ describe('', () => { }); test('DELETED is shown for required Job resources that have been deleted', () => { - wrapper = mountWithContexts( - - ); + const newMockData = { + ...mockJobData, + summary_fields: { + ...mockJobData.summary_fields, + inventory: null, + project: null, + }, + }; + wrapper = mountWithContexts(); const detail = wrapper.find('JobDetail'); async function assertMissingDetail(label) { expect(detail.length).toBe(1); @@ -177,4 +179,128 @@ describe('', () => { ); assertDetail('Job Type', 'Playbook Check'); }); + + test('should not show cancel job button, not super user', () => { + const history = createMemoryHistory({ + initialEntries: ['/settings/miscellaneous_system/edit'], + }); + + wrapper = mountWithContexts( + , + { + context: { + router: { + history, + }, + config: { + me: { + is_superuser: false, + }, + }, + }, + } + ); + expect( + wrapper.find('Button[aria-label="Cancel Demo Job Template"]') + ).toHaveLength(0); + }); + + test('should not show cancel job button, job completed', async () => { + const history = createMemoryHistory({ + initialEntries: ['/settings/miscellaneous_system/edit'], + }); + + wrapper = mountWithContexts( + , + { + context: { + router: { + history, + }, + config: { + me: { + is_superuser: true, + }, + }, + }, + } + ); + expect( + wrapper.find('Button[aria-label="Cancel Demo Job Template"]') + ).toHaveLength(0); + }); + + test('should show cancel button, pending, super user', async () => { + const history = createMemoryHistory({ + initialEntries: ['/settings/miscellaneous_system/edit'], + }); + + wrapper = mountWithContexts( + , + { + context: { + router: { + history, + }, + config: { + me: { + is_superuser: true, + }, + }, + }, + } + ); + expect( + wrapper.find('Button[aria-label="Cancel Demo Job Template"]') + ).toHaveLength(1); + }); + + test('should show cancel button, pending, super project update, not super user', async () => { + const history = createMemoryHistory({ + initialEntries: ['/settings/miscellaneous_system/edit'], + }); + + wrapper = mountWithContexts( + , + { + context: { + router: { + history, + }, + config: { + me: { + is_superuser: false, + }, + }, + }, + } + ); + expect( + wrapper.find('Button[aria-label="Cancel Demo Job Template"]') + ).toHaveLength(1); + }); }); diff --git a/awx/ui_next/src/screens/Job/JobOutput/shared/OutputToolbar.jsx b/awx/ui_next/src/screens/Job/JobOutput/shared/OutputToolbar.jsx index e42624945e..6a600d245e 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/shared/OutputToolbar.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/shared/OutputToolbar.jsx @@ -14,6 +14,8 @@ import { LaunchButton, ReLaunchDropDown, } from '../../../../components/LaunchButton'; +import { useConfig } from '../../../../contexts/Config'; + import JobCancelButton from '../../../../components/JobCancelButton'; const BadgeGroup = styled.div` @@ -73,6 +75,7 @@ const OutputToolbar = ({ job, onDelete, isDeleteDisabled, jobStatus }) => { (sum, key) => sum + job?.host_status_counts[key], 0 ); + const { me } = useConfig(); return ( @@ -125,9 +128,10 @@ const OutputToolbar = ({ job, onDelete, isDeleteDisabled, jobStatus }) => { {toHHMMSS(job.elapsed)} - - {job.summary_fields.user_capabilities?.start && - (['pending', 'waiting', 'running'].includes(jobStatus) ? ( + {['pending', 'waiting', 'running'].includes(jobStatus) && + (job.type === 'system_job' + ? me.is_superuser + : job?.summary_fields?.user_capabilities?.start) && ( { errorMessage={t`Failed to cancel ${job.name}`} showIconButton /> - ) : ( - - {job.status === 'failed' && job.type === 'job' ? ( - - {({ handleRelaunch, isLaunching }) => ( - - )} - - ) : ( - - {({ handleRelaunch, isLaunching }) => ( - - )} - - )} - - ))} + )} + {job.summary_fields.user_capabilities?.start && ( + + {job.status === 'failed' && job.type === 'job' ? ( + + {({ handleRelaunch, isLaunching }) => ( + + )} + + ) : ( + + {({ handleRelaunch, isLaunching }) => ( + + )} + + )} + + )} {job.related?.stdout && ( @@ -184,16 +189,6 @@ const OutputToolbar = ({ job, onDelete, isDeleteDisabled, jobStatus }) => { )} - {/* {job.summary_fields.user_capabilities.start && - ['pending', 'waiting', 'running'].includes(jobStatus) && ( - - )} */} {job.summary_fields.user_capabilities.delete && ['new', 'successful', 'failed', 'error', 'canceled'].includes( jobStatus diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 2775d5fb4c..675b39c6d0 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -207,6 +207,7 @@ function ProjectDetail({ project }) { errorTitle={t`Project Sync Error`} title={t`Cancel Project Sync`} errorMessage={t`Failed to cancel Project Sync`} + buttonText={t`Cancel Sync`} /> ) : (