From af90a78df57ac12975b0b33a1e754a4bb2544ca4 Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 20 Sep 2019 10:55:30 -0400 Subject: [PATCH 1/3] Extends LaunchButton component to include support for relaunching. Adds relaunch button to jobs list and job detail view(s). --- awx/ui_next/src/api/index.js | 3 + .../src/api/mixins/LaunchUpdate.mixin.js | 12 ++++ awx/ui_next/src/api/mixins/Relaunch.mixin.js | 12 ++++ awx/ui_next/src/api/models/AdHocCommands.js | 3 +- .../src/api/models/InventorySources.js | 11 +++ .../src/api/models/InventoryUpdates.js | 3 +- awx/ui_next/src/api/models/Jobs.js | 3 +- awx/ui_next/src/api/models/Projects.js | 3 +- awx/ui_next/src/api/models/WorkflowJobs.js | 3 +- .../components/LaunchButton/LaunchButton.jsx | 70 +++++++++++++++++-- .../LaunchButton/LaunchButton.test.jsx | 16 +++-- .../src/screens/Job/JobDetail/JobDetail.jsx | 11 +++ .../src/screens/Job/JobList/JobListItem.jsx | 37 +++++++++- .../screens/Job/JobList/JobListItem.test.jsx | 5 ++ .../JobTemplateDetail/JobTemplateDetail.jsx | 10 +-- .../TemplateList/TemplateListItem.jsx | 8 +-- 16 files changed, 177 insertions(+), 33 deletions(-) create mode 100644 awx/ui_next/src/api/mixins/LaunchUpdate.mixin.js create mode 100644 awx/ui_next/src/api/mixins/Relaunch.mixin.js create mode 100644 awx/ui_next/src/api/models/InventorySources.js diff --git a/awx/ui_next/src/api/index.js b/awx/ui_next/src/api/index.js index 128b9aa706..41bef37154 100644 --- a/awx/ui_next/src/api/index.js +++ b/awx/ui_next/src/api/index.js @@ -2,6 +2,7 @@ import AdHocCommands from './models/AdHocCommands'; import Config from './models/Config'; import InstanceGroups from './models/InstanceGroups'; import Inventories from './models/Inventories'; +import InventorySources from './models/InventorySources'; import InventoryUpdates from './models/InventoryUpdates'; import JobTemplates from './models/JobTemplates'; import Jobs from './models/Jobs'; @@ -23,6 +24,7 @@ const AdHocCommandsAPI = new AdHocCommands(); const ConfigAPI = new Config(); const InstanceGroupsAPI = new InstanceGroups(); const InventoriesAPI = new Inventories(); +const InventorySourcesAPI = new InventorySources(); const InventoryUpdatesAPI = new InventoryUpdates(); const JobTemplatesAPI = new JobTemplates(); const JobsAPI = new Jobs(); @@ -45,6 +47,7 @@ export { ConfigAPI, InstanceGroupsAPI, InventoriesAPI, + InventorySourcesAPI, InventoryUpdatesAPI, JobTemplatesAPI, JobsAPI, diff --git a/awx/ui_next/src/api/mixins/LaunchUpdate.mixin.js b/awx/ui_next/src/api/mixins/LaunchUpdate.mixin.js new file mode 100644 index 0000000000..a4e62afb20 --- /dev/null +++ b/awx/ui_next/src/api/mixins/LaunchUpdate.mixin.js @@ -0,0 +1,12 @@ +const LaunchUpdateMixin = parent => + class extends parent { + launchUpdate(id, data) { + return this.http.post(`${this.baseUrl}${id}/update/`, data); + } + + readLaunchUpdate(id) { + return this.http.get(`${this.baseUrl}${id}/update/`); + } + }; + +export default LaunchUpdateMixin; diff --git a/awx/ui_next/src/api/mixins/Relaunch.mixin.js b/awx/ui_next/src/api/mixins/Relaunch.mixin.js new file mode 100644 index 0000000000..06594c6dd3 --- /dev/null +++ b/awx/ui_next/src/api/mixins/Relaunch.mixin.js @@ -0,0 +1,12 @@ +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/models/AdHocCommands.js b/awx/ui_next/src/api/models/AdHocCommands.js index 1bfd78e9cb..4879b81b32 100644 --- a/awx/ui_next/src/api/models/AdHocCommands.js +++ b/awx/ui_next/src/api/models/AdHocCommands.js @@ -1,6 +1,7 @@ import Base from '../Base'; +import RelaunchMixin from '../mixins/Relaunch.mixin'; -class AdHocCommands extends Base { +class AdHocCommands extends RelaunchMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/ad_hoc_commands/'; diff --git a/awx/ui_next/src/api/models/InventorySources.js b/awx/ui_next/src/api/models/InventorySources.js new file mode 100644 index 0000000000..be43f988eb --- /dev/null +++ b/awx/ui_next/src/api/models/InventorySources.js @@ -0,0 +1,11 @@ +import Base from '../Base'; +import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin'; + +class InventorySources extends LaunchUpdateMixin(Base) { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/inventory_sources/'; + } +} + +export default InventorySources; diff --git a/awx/ui_next/src/api/models/InventoryUpdates.js b/awx/ui_next/src/api/models/InventoryUpdates.js index 0b30042e2c..a4dc05b392 100644 --- a/awx/ui_next/src/api/models/InventoryUpdates.js +++ b/awx/ui_next/src/api/models/InventoryUpdates.js @@ -1,6 +1,7 @@ import Base from '../Base'; +import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin'; -class InventoryUpdates extends Base { +class InventoryUpdates extends LaunchUpdateMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/inventory_updates/'; diff --git a/awx/ui_next/src/api/models/Jobs.js b/awx/ui_next/src/api/models/Jobs.js index 1a01ace0ba..a0f2f874fd 100644 --- a/awx/ui_next/src/api/models/Jobs.js +++ b/awx/ui_next/src/api/models/Jobs.js @@ -1,4 +1,5 @@ import Base from '../Base'; +import RelaunchMixin from '../mixins/Relaunch.mixin'; const BASE_URLS = { playbook: '/jobs/', @@ -9,7 +10,7 @@ const BASE_URLS = { workflow: '/workflow_jobs/', }; -class Jobs extends Base { +class Jobs extends RelaunchMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/jobs/'; diff --git a/awx/ui_next/src/api/models/Projects.js b/awx/ui_next/src/api/models/Projects.js index c9278cf4a0..14a6048b0a 100644 --- a/awx/ui_next/src/api/models/Projects.js +++ b/awx/ui_next/src/api/models/Projects.js @@ -1,6 +1,7 @@ import Base from '../Base'; +import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin'; -class Projects extends Base { +class Projects extends LaunchUpdateMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/projects/'; diff --git a/awx/ui_next/src/api/models/WorkflowJobs.js b/awx/ui_next/src/api/models/WorkflowJobs.js index dc484b1bce..8a7102cc99 100644 --- a/awx/ui_next/src/api/models/WorkflowJobs.js +++ b/awx/ui_next/src/api/models/WorkflowJobs.js @@ -1,6 +1,7 @@ import Base from '../Base'; +import RelaunchMixin from '../mixins/Relaunch.mixin'; -class WorkflowJobs extends Base { +class WorkflowJobs extends RelaunchMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/workflow_jobs/'; diff --git a/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx b/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx index 6e7c733319..7137f44860 100644 --- a/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx +++ b/awx/ui_next/src/components/LaunchButton/LaunchButton.jsx @@ -1,16 +1,25 @@ import React, { Fragment } from 'react'; import { withRouter } from 'react-router-dom'; -import { number } from 'prop-types'; +import { number, shape } from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import AlertModal from '@components/AlertModal'; import ErrorDetail from '@components/ErrorDetail'; -import { JobTemplatesAPI } from '@api'; +import { + AdHocCommandsAPI, + InventorySourcesAPI, + JobsAPI, + JobTemplatesAPI, + ProjectsAPI, + WorkflowJobsAPI, +} from '@api'; class LaunchButton extends React.Component { static propTypes = { - templateId: number.isRequired, + resource: shape({ + id: number.isRequired, + }).isRequired, }; constructor(props) { @@ -22,6 +31,7 @@ class LaunchButton extends React.Component { }; this.handleLaunch = this.handleLaunch.bind(this); + this.handleRelaunch = this.handleRelaunch.bind(this); this.handleLaunchErrorClose = this.handleLaunchErrorClose.bind(this); this.handlePromptErrorClose = this.handlePromptErrorClose.bind(this); } @@ -35,13 +45,56 @@ class LaunchButton extends React.Component { } async handleLaunch() { - const { history, templateId } = this.props; + const { history, resource } = this.props; try { const { data: launchConfig } = await JobTemplatesAPI.readLaunch( - templateId + resource.id ); if (launchConfig.can_start_without_user_input) { - const { data: job } = await JobTemplatesAPI.launch(templateId); + const { data: job } = await JobTemplatesAPI.launch(resource.id); + history.push(`/jobs/${job.id}/details`); + } else { + this.setState({ promptError: true }); + } + } catch (err) { + this.setState({ launchError: err }); + } + } + + async handleRelaunch() { + const { history, resource } = this.props; + + let readRelaunch; + let relaunch; + + if (resource.type === 'inventory_update') { + // We'll need to handle the scenario where the src no longer exists + readRelaunch = InventorySourcesAPI.readLaunchUpdate( + resource.inventory_source + ); + relaunch = InventorySourcesAPI.launchUpdate(resource.inventory_source); + } else if (resource.type === 'project_update') { + // We'll need to handle the scenario where the project no longer exists + readRelaunch = ProjectsAPI.readLaunchUpdate(resource.project); + relaunch = ProjectsAPI.launchUpdate(resource.project); + } else if (resource.type === 'workflow_job') { + readRelaunch = WorkflowJobsAPI.readRelaunch(resource.id); + relaunch = WorkflowJobsAPI.relaunch(resource.id); + } else if (resource.type === 'ad_hoc_command') { + readRelaunch = AdHocCommandsAPI.readRelaunch(resource.id); + relaunch = AdHocCommandsAPI.relaunch(resource.id); + } else if (resource.type === 'job') { + readRelaunch = JobsAPI.readRelaunch(resource.id); + relaunch = JobsAPI.relaunch(resource.id); + } + + try { + const { data: relaunchConfig } = await readRelaunch; + if ( + !relaunchConfig.passwords_needed_to_start || + relaunchConfig.passwords_needed_to_start.length === 0 + ) { + const { data: job } = await relaunch; history.push(`/jobs/${job.id}/details`); } else { this.setState({ promptError: true }); @@ -56,7 +109,10 @@ class LaunchButton extends React.Component { const { i18n, children } = this.props; return ( - {children(this.handleLaunch)} + {children({ + handleLaunch: this.handleLaunch, + handleRelaunch: this.handleRelaunch, + })} { can_start_without_user_input: true, }, }); - const children = handleLaunch => ( - + )} + + )} )} {canLaunch && ( - - {handleLaunch => ( + + {({ handleLaunch }) => (