diff --git a/awx/ui_next/src/api/mixins/Relaunch.mixin.js b/awx/ui_next/src/api/mixins/Relaunch.mixin.js
deleted file mode 100644
index 06594c6dd3..0000000000
--- a/awx/ui_next/src/api/mixins/Relaunch.mixin.js
+++ /dev/null
@@ -1,12 +0,0 @@
-const RelaunchMixin = parent =>
- class extends parent {
- relaunch(id, data) {
- return this.http.post(`${this.baseUrl}${id}/relaunch/`, data);
- }
-
- readRelaunch(id) {
- return this.http.get(`${this.baseUrl}${id}/relaunch/`);
- }
- };
-
-export default RelaunchMixin;
diff --git a/awx/ui_next/src/api/mixins/Runnable.mixin.js b/awx/ui_next/src/api/mixins/Runnable.mixin.js
new file mode 100644
index 0000000000..ba8aac8681
--- /dev/null
+++ b/awx/ui_next/src/api/mixins/Runnable.mixin.js
@@ -0,0 +1,48 @@
+const Runnable = parent =>
+ class extends parent {
+ jobEventSlug = '/events/';
+
+ cancel(id) {
+ const endpoint = `${this.baseUrl}${id}/cancel/`;
+
+ return this.http.post(endpoint);
+ }
+
+ launchUpdate(id, data) {
+ const endpoint = `${this.baseUrl}${id}/update/`;
+
+ return this.http.post(endpoint, data);
+ }
+
+ readLaunchUpdate(id) {
+ const endpoint = `${this.baseUrl}${id}/update/`;
+
+ return this.http.get(endpoint);
+ }
+
+ readEvents(id, params = {}) {
+ const endpoint = `${this.baseUrl}${id}${this.jobEventSlug}`;
+
+ return this.http.get(endpoint, { params });
+ }
+
+ readEventOptions(id) {
+ const endpoint = `${this.baseUrl}${id}${this.jobEventSlug}`;
+
+ return this.http.options(endpoint);
+ }
+
+ readRelaunch(id) {
+ const endpoint = `${this.baseUrl}${id}/relaunch/`;
+
+ return this.http.get(endpoint);
+ }
+
+ relaunch(id, data) {
+ const endpoint = `${this.baseUrl}${id}/relaunch/`;
+
+ return this.http.post(endpoint, data);
+ }
+ };
+
+export default Runnable;
diff --git a/awx/ui_next/src/api/models/AdHocCommands.js b/awx/ui_next/src/api/models/AdHocCommands.js
index 4879b81b32..2db8e7ddf8 100644
--- a/awx/ui_next/src/api/models/AdHocCommands.js
+++ b/awx/ui_next/src/api/models/AdHocCommands.js
@@ -1,11 +1,15 @@
import Base from '../Base';
-import RelaunchMixin from '../mixins/Relaunch.mixin';
+import RunnableMixin from '../mixins/Runnable.mixin';
-class AdHocCommands extends RelaunchMixin(Base) {
+class AdHocCommands extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/ad_hoc_commands/';
}
+
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
+ }
}
export default AdHocCommands;
diff --git a/awx/ui_next/src/api/models/InventoryUpdates.js b/awx/ui_next/src/api/models/InventoryUpdates.js
index 1700c7b26b..0d917b0aeb 100644
--- a/awx/ui_next/src/api/models/InventoryUpdates.js
+++ b/awx/ui_next/src/api/models/InventoryUpdates.js
@@ -1,7 +1,7 @@
import Base from '../Base';
-import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin';
+import RunnableMixin from '../mixins/Runnable.mixin';
-class InventoryUpdates extends LaunchUpdateMixin(Base) {
+class InventoryUpdates extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/inventory_updates/';
@@ -11,5 +11,9 @@ class InventoryUpdates extends LaunchUpdateMixin(Base) {
createSyncCancel(sourceId) {
return this.http.post(`${this.baseUrl}${sourceId}/cancel/`);
}
+
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
+ }
}
export default InventoryUpdates;
diff --git a/awx/ui_next/src/api/models/Jobs.js b/awx/ui_next/src/api/models/Jobs.js
index 026ae671f0..ae3b94cc31 100644
--- a/awx/ui_next/src/api/models/Jobs.js
+++ b/awx/ui_next/src/api/models/Jobs.js
@@ -1,67 +1,23 @@
import Base from '../Base';
-import RelaunchMixin from '../mixins/Relaunch.mixin';
+import RunnableMixin from '../mixins/Runnable.mixin';
-const getBaseURL = type => {
- switch (type) {
- case 'playbook':
- case 'job':
- return '/jobs/';
- case 'project':
- case 'project_update':
- return '/project_updates/';
- case 'management':
- case 'management_job':
- return '/system_jobs/';
- case 'inventory':
- case 'inventory_update':
- return '/inventory_updates/';
- case 'command':
- case 'ad_hoc_command':
- return '/ad_hoc_commands/';
- case 'workflow':
- case 'workflow_job':
- return '/workflow_jobs/';
- default:
- throw new Error('Unable to find matching job type');
- }
-};
-
-class Jobs extends RelaunchMixin(Base) {
+class Jobs extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/jobs/';
+ this.jobEventSlug = '/job_events/';
}
- cancel(id, type) {
- return this.http.post(`/api/v2${getBaseURL(type)}${id}/cancel/`);
+ cancel(id) {
+ return this.http.post(`${this.baseUrl}${id}/cancel/`);
}
- readCredentials(id, type) {
- return this.http.get(`/api/v2${getBaseURL(type)}${id}/credentials/`);
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
}
- readDetail(id, type) {
- return this.http.get(`/api/v2${getBaseURL(type)}${id}/`);
- }
-
- readEvents(id, type = 'playbook', params = {}) {
- let endpoint;
- if (type === 'playbook') {
- endpoint = `/api/v2${getBaseURL(type)}${id}/job_events/`;
- } else {
- endpoint = `/api/v2${getBaseURL(type)}${id}/events/`;
- }
- return this.http.get(endpoint, { params });
- }
-
- readEventOptions(id, type = 'playbook') {
- let endpoint;
- if (type === 'playbook') {
- endpoint = `/api/v2${getBaseURL(type)}${id}/job_events/`;
- } else {
- endpoint = `/api/v2${getBaseURL(type)}${id}/events/`;
- }
- return this.http.options(endpoint);
+ readDetail(id) {
+ return this.http.get(`${this.baseUrl}${id}/`);
}
}
diff --git a/awx/ui_next/src/api/models/ProjectUpdates.js b/awx/ui_next/src/api/models/ProjectUpdates.js
index 46d0633f0d..3925ae95e9 100644
--- a/awx/ui_next/src/api/models/ProjectUpdates.js
+++ b/awx/ui_next/src/api/models/ProjectUpdates.js
@@ -1,10 +1,15 @@
import Base from '../Base';
+import RunnableMixin from '../mixins/Runnable.mixin';
-class ProjectUpdates extends Base {
+class ProjectUpdates extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/project_updates/';
}
+
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
+ }
}
export default ProjectUpdates;
diff --git a/awx/ui_next/src/api/models/SystemJobs.js b/awx/ui_next/src/api/models/SystemJobs.js
index d7b6ec1750..8365f6f65b 100644
--- a/awx/ui_next/src/api/models/SystemJobs.js
+++ b/awx/ui_next/src/api/models/SystemJobs.js
@@ -1,10 +1,16 @@
import Base from '../Base';
-class SystemJobs extends Base {
+import RunnableMixin from '../mixins/Runnable.mixin';
+
+class SystemJobs extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/system_jobs/';
}
+
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
+ }
}
export default SystemJobs;
diff --git a/awx/ui_next/src/api/models/WorkflowJobs.js b/awx/ui_next/src/api/models/WorkflowJobs.js
index 87e336e8f5..f2799973b0 100644
--- a/awx/ui_next/src/api/models/WorkflowJobs.js
+++ b/awx/ui_next/src/api/models/WorkflowJobs.js
@@ -1,7 +1,7 @@
import Base from '../Base';
-import RelaunchMixin from '../mixins/Relaunch.mixin';
+import RunnableMixin from '../mixins/Runnable.mixin';
-class WorkflowJobs extends RelaunchMixin(Base) {
+class WorkflowJobs extends RunnableMixin(Base) {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/workflow_jobs/';
@@ -10,6 +10,10 @@ class WorkflowJobs extends RelaunchMixin(Base) {
readNodes(id, params) {
return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params });
}
+
+ readCredentials(id) {
+ return this.http.get(`${this.baseUrl}${id}/credentials/`);
+ }
}
export default WorkflowJobs;
diff --git a/awx/ui_next/src/components/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx
index 5c331fd8a5..c92096ad68 100644
--- a/awx/ui_next/src/components/JobList/JobList.jsx
+++ b/awx/ui_next/src/components/JobList/JobList.jsx
@@ -13,20 +13,12 @@ import useRequest, {
useDeleteItems,
useDismissableError,
} from '../../util/useRequest';
-import isJobRunning from '../../util/jobs';
+import { isJobRunning, getJobModel } from '../../util/jobs';
import { getQSConfig, parseQueryString } from '../../util/qs';
import JobListItem from './JobListItem';
import JobListCancelButton from './JobListCancelButton';
import useWsJobs from './useWsJobs';
-import {
- AdHocCommandsAPI,
- InventoryUpdatesAPI,
- JobsAPI,
- ProjectUpdatesAPI,
- SystemJobsAPI,
- UnifiedJobsAPI,
- WorkflowJobsAPI,
-} from '../../api';
+import { UnifiedJobsAPI } from '../../api';
function JobList({ i18n, defaultParams, showTypeColumn = false }) {
const qsConfig = getQSConfig(
@@ -104,7 +96,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
return Promise.all(
selected.map(job => {
if (isJobRunning(job.status)) {
- return JobsAPI.cancel(job.id, job.type);
+ return getJobModel(job.type).cancel(job.id);
}
return Promise.resolve();
})
@@ -127,22 +119,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
useCallback(() => {
return Promise.all(
selected.map(({ type, id }) => {
- switch (type) {
- case 'job':
- return JobsAPI.destroy(id);
- case 'ad_hoc_command':
- return AdHocCommandsAPI.destroy(id);
- case 'system_job':
- return SystemJobsAPI.destroy(id);
- case 'project_update':
- return ProjectUpdatesAPI.destroy(id);
- case 'inventory_update':
- return InventoryUpdatesAPI.destroy(id);
- case 'workflow_job':
- return WorkflowJobsAPI.destroy(id);
- default:
- return null;
- }
+ return getJobModel(type).destroy(id);
})
);
}, [selected]),
diff --git a/awx/ui_next/src/components/JobList/JobList.test.jsx b/awx/ui_next/src/components/JobList/JobList.test.jsx
index 87f74abfeb..45451de8dd 100644
--- a/awx/ui_next/src/components/JobList/JobList.test.jsx
+++ b/awx/ui_next/src/components/JobList/JobList.test.jsx
@@ -319,13 +319,12 @@ describe('', () => {
wrapper.find('JobListCancelButton').invoke('onCancel')();
});
- expect(JobsAPI.cancel).toHaveBeenCalledTimes(6);
- expect(JobsAPI.cancel).toHaveBeenCalledWith(1, 'project_update');
- expect(JobsAPI.cancel).toHaveBeenCalledWith(2, 'job');
- expect(JobsAPI.cancel).toHaveBeenCalledWith(3, 'inventory_update');
- expect(JobsAPI.cancel).toHaveBeenCalledWith(4, 'workflow_job');
- expect(JobsAPI.cancel).toHaveBeenCalledWith(5, 'system_job');
- expect(JobsAPI.cancel).toHaveBeenCalledWith(6, 'ad_hoc_command');
+ expect(ProjectUpdatesAPI.cancel).toHaveBeenCalledWith(1);
+ expect(JobsAPI.cancel).toHaveBeenCalledWith(2);
+ expect(InventoryUpdatesAPI.cancel).toHaveBeenCalledWith(3);
+ expect(WorkflowJobsAPI.cancel).toHaveBeenCalledWith(4);
+ expect(SystemJobsAPI.cancel).toHaveBeenCalledWith(5);
+ expect(AdHocCommandsAPI.cancel).toHaveBeenCalledWith(6);
jest.restoreAllMocks();
});
diff --git a/awx/ui_next/src/components/JobList/JobListCancelButton.jsx b/awx/ui_next/src/components/JobList/JobListCancelButton.jsx
index 6f008552b7..efad12993b 100644
--- a/awx/ui_next/src/components/JobList/JobListCancelButton.jsx
+++ b/awx/ui_next/src/components/JobList/JobListCancelButton.jsx
@@ -4,7 +4,7 @@ import { t } from '@lingui/macro';
import { arrayOf, func } from 'prop-types';
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
import { KebabifiedContext } from '../../contexts/Kebabified';
-import isJobRunning from '../../util/jobs';
+import { isJobRunning } from '../../util/jobs';
import AlertModal from '../AlertModal';
import { Job } from '../../types';
diff --git a/awx/ui_next/src/screens/Job/Job.jsx b/awx/ui_next/src/screens/Job/Job.jsx
index 8c058bbdf6..a46315c397 100644
--- a/awx/ui_next/src/screens/Job/Job.jsx
+++ b/awx/ui_next/src/screens/Job/Job.jsx
@@ -12,20 +12,32 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { CaretLeftIcon } from '@patternfly/react-icons';
import { Card, PageSection } from '@patternfly/react-core';
-import { JobsAPI } from '../../api';
import ContentError from '../../components/ContentError';
import ContentLoading from '../../components/ContentLoading';
import RoutedTabs from '../../components/RoutedTabs';
import useRequest from '../../util/useRequest';
+import { getJobModel } from '../../util/jobs';
import JobDetail from './JobDetail';
import JobOutput from './JobOutput';
import { WorkflowOutput } from './WorkflowOutput';
import useWsJob from './useWsJob';
+// maps the displayed url segments to actual api types
+export const JOB_URL_SEGMENT_MAP = {
+ playbook: 'job',
+ project: 'project_update',
+ management: 'system_job',
+ inventory: 'inventory_update',
+ command: 'ad_hoc_command',
+ workflow: 'workflow_job',
+};
+
function Job({ i18n, setBreadcrumb }) {
- const { id, type } = useParams();
+ const { id, typeSegment } = useParams();
const match = useRouteMatch();
+ const type = JOB_URL_SEGMENT_MAP[typeSegment];
+
const {
isLoading,
error,
@@ -34,12 +46,11 @@ function Job({ i18n, setBreadcrumb }) {
} = useRequest(
useCallback(async () => {
let eventOptions = {};
- const { data: jobDetailData } = await JobsAPI.readDetail(id, type);
- if (jobDetailData.type !== 'workflow_job') {
- const { data: jobEventOptions } = await JobsAPI.readEventOptions(
- id,
+ const { data: jobDetailData } = await getJobModel(type).readDetail(id);
+ if (type !== 'workflow_job') {
+ const { data: jobEventOptions } = await getJobModel(
type
- );
+ ).readEventOptions(id);
eventOptions = jobEventOptions;
}
if (
@@ -49,7 +60,7 @@ function Job({ i18n, setBreadcrumb }) {
) {
const {
data: { results },
- } = await JobsAPI.readCredentials(jobDetailData.id, type);
+ } = await getJobModel(type).readCredentials(jobDetailData.id);
jobDetailData.summary_fields.credentials = results;
}
@@ -125,37 +136,37 @@ function Job({ i18n, setBreadcrumb }) {
-
- {job &&
- job.type === 'workflow_job' && [
-
-
- ,
-
+
+ {job && [
+
+
+ ,
+
+ {job.type === 'workflow_job' ? (
- ,
- ]}
- {job &&
- job.type !== 'workflow_job' && [
-
-
- ,
-
+ ) : (
- ,
-
-
-
- {i18n._(t`View Job Details`)}
-
-
- ,
- ]}
+ )}
+ ,
+
+
+
+ {i18n._(t`View Job Details`)}
+
+
+ ,
+ ]}
diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx
index 678b4c24bc..c4a128eaf9 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,17 +25,11 @@ import {
} from '../../../components/LaunchButton';
import StatusIcon from '../../../components/StatusIcon';
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
+import { getJobModel, 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 {
- JobsAPI,
- ProjectUpdatesAPI,
- SystemJobsAPI,
- WorkflowJobsAPI,
- InventoriesAPI,
- AdHocCommandsAPI,
-} from '../../../api';
const VariablesInput = styled(_VariablesInput)`
.pf-c-form__label {
@@ -77,6 +71,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 +103,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 +404,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 && (
{
- await JobsAPI.cancel(job.id, type);
- }, [job.id, type]),
+ await getJobModel(job.type).cancel(job.id);
+ }, [job.id, job.type]),
{}
);
@@ -364,27 +351,10 @@ function JobOutput({
error: deleteError,
} = useRequest(
useCallback(async () => {
- 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');
- }, [job, history])
+ }, [job.type, job.id, history])
);
const {
@@ -417,7 +387,7 @@ function JobOutput({
try {
const {
data: { results: fetchedEvents = [], count },
- } = await JobsAPI.readEvents(job.id, type, {
+ } = await getJobModel(job.type).readEvents(job.id, {
page: 1,
page_size: 50,
...parseQueryString(QS_CONFIG, location.search),
@@ -557,31 +527,33 @@ function JobOutput({
...parseQueryString(QS_CONFIG, location.search),
};
- return JobsAPI.readEvents(job.id, type, params).then(response => {
- if (isMounted.current) {
- const newResults = {};
- let newResultsCssMap = {};
- response.data.results.forEach((jobEvent, index) => {
- newResults[firstIndex + index] = jobEvent;
- const { lineCssMap } = getLineTextHtml(jobEvent);
- newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
- });
- setResults(prevResults => ({
- ...prevResults,
- ...newResults,
- }));
- setCssMap(prevCssMap => ({
- ...prevCssMap,
- ...newResultsCssMap,
- }));
- setCurrentlyLoading(prevCurrentlyLoading =>
- prevCurrentlyLoading.filter(n => !loadRange.includes(n))
- );
- loadRange.forEach(n => {
- cache.clear(n);
- });
- }
- });
+ return getJobModel(job.type)
+ .readEvents(job.id, params)
+ .then(response => {
+ if (isMounted.current) {
+ const newResults = {};
+ let newResultsCssMap = {};
+ response.data.results.forEach((jobEvent, index) => {
+ newResults[firstIndex + index] = jobEvent;
+ const { lineCssMap } = getLineTextHtml(jobEvent);
+ newResultsCssMap = { ...newResultsCssMap, ...lineCssMap };
+ });
+ setResults(prevResults => ({
+ ...prevResults,
+ ...newResults,
+ }));
+ setCssMap(prevCssMap => ({
+ ...prevCssMap,
+ ...newResultsCssMap,
+ }));
+ setCurrentlyLoading(prevCurrentlyLoading =>
+ prevCurrentlyLoading.filter(n => !loadRange.includes(n))
+ );
+ loadRange.forEach(n => {
+ cache.clear(n);
+ });
+ }
+ });
};
const scrollToRow = rowIndex => {
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
index 59efdfe323..72354fe32a 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.test.jsx
@@ -278,7 +278,7 @@ describe('', () => {
wrapper.find(searchBtn).simulate('click');
});
wrapper.update();
- expect(JobsAPI.readEvents).toHaveBeenCalledWith(2, undefined, {
+ expect(JobsAPI.readEvents).toHaveBeenCalledWith(2, {
order_by: 'start_line',
page: 1,
page_size: 50,
diff --git a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
index dbf2256fdc..22b70c7f17 100644
--- a/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
+++ b/awx/ui_next/src/screens/Job/JobTypeRedirect.jsx
@@ -55,8 +55,8 @@ function JobTypeRedirect({ id, path, view, i18n }) {
);
}
- const type = JOB_TYPE_URL_SEGMENTS[job.type];
- return ;
+ const typeSegment = JOB_TYPE_URL_SEGMENTS[job.type];
+ return ;
}
JobTypeRedirect.defaultProps = {
diff --git a/awx/ui_next/src/screens/Job/Jobs.jsx b/awx/ui_next/src/screens/Job/Jobs.jsx
index 318729407a..f75d560d70 100644
--- a/awx/ui_next/src/screens/Job/Jobs.jsx
+++ b/awx/ui_next/src/screens/Job/Jobs.jsx
@@ -21,12 +21,12 @@ function Jobs({ i18n }) {
return;
}
- const type = JOB_TYPE_URL_SEGMENTS[job.type];
+ const typeSegment = JOB_TYPE_URL_SEGMENTS[job.type];
setBreadcrumbConfig({
'/jobs': i18n._(t`Jobs`),
- [`/jobs/${type}/${job.id}`]: `${job.name}`,
- [`/jobs/${type}/${job.id}/output`]: i18n._(t`Output`),
- [`/jobs/${type}/${job.id}/details`]: i18n._(t`Details`),
+ [`/jobs/${typeSegment}/${job.id}`]: `${job.name}`,
+ [`/jobs/${typeSegment}/${job.id}/output`]: i18n._(t`Output`),
+ [`/jobs/${typeSegment}/${job.id}/details`]: i18n._(t`Details`),
});
},
[i18n]
@@ -53,7 +53,7 @@ function Jobs({ i18n }) {
-
+
diff --git a/awx/ui_next/src/screens/Job/useWsJob.js b/awx/ui_next/src/screens/Job/useWsJob.js
index ace2cf2ce6..e9461888d7 100644
--- a/awx/ui_next/src/screens/Job/useWsJob.js
+++ b/awx/ui_next/src/screens/Job/useWsJob.js
@@ -1,10 +1,8 @@
import { useState, useEffect } from 'react';
-import { useParams } from 'react-router-dom';
import useWebsocket from '../../util/useWebsocket';
-import { JobsAPI } from '../../api';
+import { getJobModel } from '../../util/jobs';
export default function useWsJob(initialJob) {
- const { type } = useParams();
const [job, setJob] = useState(initialJob);
const lastMessage = useWebsocket({
jobs: ['status_changed'],
@@ -18,7 +16,7 @@ export default function useWsJob(initialJob) {
useEffect(
function parseWsMessage() {
async function fetchJob() {
- const { data } = await JobsAPI.readDetail(job.id, type);
+ const { data } = await getJobModel(job.type).readDetail(job.id);
setJob(data);
}
diff --git a/awx/ui_next/src/util/jobs.js b/awx/ui_next/src/util/jobs.js
index e4129388a5..de227ffc59 100644
--- a/awx/ui_next/src/util/jobs.js
+++ b/awx/ui_next/src/util/jobs.js
@@ -1,3 +1,22 @@
-export default function isJobRunning(status) {
+import {
+ JobsAPI,
+ ProjectUpdatesAPI,
+ SystemJobsAPI,
+ WorkflowJobsAPI,
+ InventoryUpdatesAPI,
+ AdHocCommandsAPI,
+} from '../api';
+
+export function isJobRunning(status) {
return ['new', 'pending', 'waiting', 'running'].includes(status);
}
+
+export function getJobModel(type) {
+ if (type === 'ad_hoc_command') return AdHocCommandsAPI;
+ if (type === 'inventory_update') return InventoryUpdatesAPI;
+ if (type === 'project_update') return ProjectUpdatesAPI;
+ if (type === 'system_job') return SystemJobsAPI;
+ if (type === 'workflow_job') return WorkflowJobsAPI;
+
+ return JobsAPI;
+}
diff --git a/awx/ui_next/src/util/jobs.test.js b/awx/ui_next/src/util/jobs.test.js
index 953b06ba17..6dcab23166 100644
--- a/awx/ui_next/src/util/jobs.test.js
+++ b/awx/ui_next/src/util/jobs.test.js
@@ -1,4 +1,4 @@
-import isJobRunning from './jobs';
+import { getJobModel, isJobRunning } from './jobs';
describe('isJobRunning', () => {
test('should return true for new', () => {
@@ -23,3 +23,23 @@ describe('isJobRunning', () => {
expect(isJobRunning('failed')).toBe(false);
});
});
+
+describe('getJobModel', () => {
+ test('should return valid job model in all cases', () => {
+ const baseUrls = [];
+ [
+ 'ad_hoc_command',
+ 'inventory_update',
+ 'project_update',
+ 'system_job',
+ 'workflow_job',
+ 'job',
+ 'default',
+ ].forEach(type => {
+ expect(getJobModel(type)).toHaveProperty('http');
+ expect(getJobModel(type).jobEventSlug).toBeDefined();
+ baseUrls.push(getJobModel(type).baseUrl);
+ });
+ expect(new Set(baseUrls).size).toBe(baseUrls.length - 1);
+ });
+});