Skip to content

Commit

Permalink
Add ability to cancel jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
jakemcdermott committed Feb 2, 2021
1 parent 89646e7 commit 339d430
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 33 deletions.
129 changes: 109 additions & 20 deletions awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import { I18n } from '@lingui/react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import styled from 'styled-components';
import {
Expand All @@ -10,6 +10,7 @@ import {
InfiniteLoader,
List,
} from 'react-virtualized';
import { Button } from '@patternfly/react-core';
import Ansi from 'ansi-to-html';
import hasAnsi from 'has-ansi';
import { AllHtmlEntities } from 'html-entities';
Expand Down Expand Up @@ -225,13 +226,17 @@ class JobOutput extends Component {
this.state = {
contentError: null,
deletionError: null,
cancelError: null,
hasContentLoading: true,
results: {},
currentlyLoading: [],
remoteRowCount: 0,
isHostModalOpen: false,
hostEvent: {},
cssMap: {},
jobStatus: props.job.status ?? 'waiting',
showCancelPrompt: false,
cancelInProgress: false,
};

this.cache = new CellMeasurerCache({
Expand All @@ -242,6 +247,9 @@ class JobOutput extends Component {
this._isMounted = false;
this.loadJobEvents = this.loadJobEvents.bind(this);
this.handleDeleteJob = this.handleDeleteJob.bind(this);
this.handleCancelOpen = this.handleCancelOpen.bind(this);
this.handleCancelConfirm = this.handleCancelConfirm.bind(this);
this.handleCancelClose = this.handleCancelClose.bind(this);
this.rowRenderer = this.rowRenderer.bind(this);
this.handleHostEventClick = this.handleHostEventClick.bind(this);
this.handleHostModalClose = this.handleHostModalClose.bind(this);
Expand All @@ -262,10 +270,18 @@ class JobOutput extends Component {
this.loadJobEvents();

connectJobSocket(job, data => {
if (data.counter && data.counter > this.jobSocketCounter) {
this.jobSocketCounter = data.counter;
} else if (data.final_counter && data.unified_job_id === job.id) {
this.jobSocketCounter = data.final_counter;
if (data.group_name === 'job_events') {
if (data.counter && data.counter > this.jobSocketCounter) {
this.jobSocketCounter = data.counter;
}
}
if (data.group_name === 'jobs' && data.unified_job_id === job.id) {
if (data.final_counter) {
this.jobSocketCounter = data.final_counter;
}
if (data.status) {
this.setState({ jobStatus: data.status });
}
}
});
this.interval = setInterval(() => this.monitorJobSocketCounter(), 5000);
Expand Down Expand Up @@ -344,6 +360,26 @@ class JobOutput extends Component {
}
}

handleCancelOpen() {
this.setState({ showCancelPrompt: true });
}

handleCancelClose() {
this.setState({ showCancelPrompt: false });
}

async handleCancelConfirm() {
const { job, type } = this.props;
this.setState({ cancelInProgress: true });
try {
await JobsAPI.cancel(job.id, type);
} catch (cancelError) {
this.setState({ cancelError });
} finally {
this.setState({ showCancelPrompt: false, cancelInProgress: false });
}
}

async handleDeleteJob() {
const { job, history } = this.props;
try {
Expand Down Expand Up @@ -518,7 +554,7 @@ class JobOutput extends Component {
}

render() {
const { job } = this.props;
const { job, i18n } = this.props;

const {
contentError,
Expand All @@ -528,6 +564,10 @@ class JobOutput extends Component {
isHostModalOpen,
remoteRowCount,
cssMap,
jobStatus,
showCancelPrompt,
cancelError,
cancelInProgress,
} = this.state;

if (hasContentLoading) {
Expand All @@ -553,7 +593,12 @@ class JobOutput extends Component {
<StatusIcon status={job.status} />
<h1>{job.name}</h1>
</HeaderTitle>
<OutputToolbar job={job} onDelete={this.handleDeleteJob} />
<OutputToolbar
job={job}
jobStatus={jobStatus}
onDelete={this.handleDeleteJob}
onCancel={this.handleCancelOpen}
/>
</OutputHeader>
<HostStatusBar counts={job.host_status_counts} />
<PageControls
Expand Down Expand Up @@ -595,21 +640,65 @@ class JobOutput extends Component {
<OutputFooter />
</OutputWrapper>
</CardBody>
{deletionError && (
<>
<I18n>
{({ i18n }) => (
<AlertModal
isOpen={deletionError}
{showCancelPrompt &&
['pending', 'waiting', 'running'].includes(jobStatus) && (
<AlertModal
isOpen={showCancelPrompt}
variant="danger"
onClose={this.handleCancelClose}
title={i18n._(t`Cancel Job`)}
label={i18n._(t`Cancel Job`)}
actions={[
<Button
id="cancel-job-confirm-button"
key="delete"
variant="danger"
onClose={() => this.setState({ deletionError: null })}
title={i18n._(t`Job Delete Error`)}
label={i18n._(t`Job Delete Error`)}
isDisabled={cancelInProgress}
aria-label={i18n._(t`Cancel job`)}
onClick={this.handleCancelConfirm}
>
{i18n._(t`Cancel job`)}
</Button>,
<Button
id="cancel-job-return-button"
key="cancel"
variant="secondary"
aria-label={i18n._(t`Return`)}
onClick={this.handleCancelClose}
>
<ErrorDetail error={deletionError} />
</AlertModal>
{i18n._(t`Return`)}
</Button>,
]}
>
{i18n._(
t`Are you sure you want to submit the request to cancel this job?`
)}
</I18n>
</AlertModal>
)}
{cancelError && (
<>
<AlertModal
isOpen={cancelError}
variant="danger"
onClose={() => this.setState({ cancelError: null })}
title={i18n._(t`Job Cancel Error`)}
label={i18n._(t`Job Cancel Error`)}
>
<ErrorDetail error={cancelError} />
</AlertModal>
</>
)}
{deletionError && (
<>
<AlertModal
isOpen={deletionError}
variant="danger"
onClose={() => this.setState({ deletionError: null })}
title={i18n._(t`Job Delete Error`)}
label={i18n._(t`Job Delete Error`)}
>
<ErrorDetail error={deletionError} />
</AlertModal>
</>
)}
</Fragment>
Expand All @@ -618,4 +707,4 @@ class JobOutput extends Component {
}

export { JobOutput as _JobOutput };
export default withRouter(JobOutput);
export default withI18n()(withRouter(JobOutput));
42 changes: 29 additions & 13 deletions awx/ui_next/src/screens/Job/JobOutput/shared/OutputToolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { shape, func } from 'prop-types';
import {
MinusCircleIcon,
DownloadIcon,
RocketIcon,
TrashAltIcon,
Expand Down Expand Up @@ -58,7 +59,7 @@ const OUTPUT_NO_COUNT_JOB_TYPES = [
'inventory_update',
];

const OutputToolbar = ({ i18n, job, onDelete }) => {
const OutputToolbar = ({ i18n, job, jobStatus, onDelete, onCancel }) => {
const hideCounts = OUTPUT_NO_COUNT_JOB_TYPES.includes(job.type);

const playCount = job?.playbook_counts?.play_count;
Expand Down Expand Up @@ -148,19 +149,34 @@ const OutputToolbar = ({ i18n, job, onDelete }) => {
</a>
</Tooltip>
)}
{job.summary_fields.user_capabilities.start &&
['pending', 'waiting', 'running'].includes(jobStatus) && (
<Tooltip content={i18n._(t`Cancel Job`)}>
<Button
variant="plain"
aria-label={i18n._(t`Cancel Job`)}
onClick={onCancel}
>
<MinusCircleIcon />
</Button>
</Tooltip>
)}

{job.summary_fields.user_capabilities.delete && (
<Tooltip content={i18n._(t`Delete Job`)}>
<DeleteButton
name={job.name}
modalTitle={i18n._(t`Delete Job`)}
onConfirm={onDelete}
variant="plain"
>
<TrashAltIcon />
</DeleteButton>
</Tooltip>
)}
{job.summary_fields.user_capabilities.delete &&
['new', 'successful', 'failed', 'error', 'canceled'].includes(
jobStatus
) && (
<Tooltip content={i18n._(t`Delete Job`)}>
<DeleteButton
name={job.name}
modalTitle={i18n._(t`Delete Job`)}
onConfirm={onDelete}
variant="plain"
>
<TrashAltIcon />
</DeleteButton>
</Tooltip>
)}
</Wrapper>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('<OutputToolbar />', () => {
failures: 2,
},
}}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand All @@ -33,6 +34,7 @@ describe('<OutputToolbar />', () => {
wrapper = mountWithContexts(
<OutputToolbar
job={{ ...mockJobData, type: 'system_job' }}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand All @@ -54,6 +56,7 @@ describe('<OutputToolbar />', () => {
host_status_counts: {},
playbook_counts: {},
}}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand All @@ -74,6 +77,7 @@ describe('<OutputToolbar />', () => {
...mockJobData,
elapsed: 274265,
}}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand All @@ -95,6 +99,7 @@ describe('<OutputToolbar />', () => {
},
},
}}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand All @@ -113,6 +118,7 @@ describe('<OutputToolbar />', () => {
},
},
}}
jobStatus="successful"
onDelete={() => {}}
/>
);
Expand Down

0 comments on commit 339d430

Please sign in to comment.