From 96fe78c394d70cb4b3788de95a20ed80a59363ab Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 30 Aug 2018 16:50:56 -0400 Subject: [PATCH 01/27] Add kebab menu with edit option to plans not started list --- .../components/Migrations/MigrationsNotStartedList.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js index 85dbec267e..53cce3d920 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { noop, Button, ListView, Grid, Icon, Spinner, Toolbar, Sort, DropdownKebab } from 'patternfly-react'; +import { noop, Button, ListView, Grid, Icon, Spinner, Toolbar, Sort, DropdownKebab, MenuItem } from 'patternfly-react'; import EllipsisWithTooltip from 'react-ellipsis-with-tooltip'; import OverviewEmptyState from '../OverviewEmptyState/OverviewEmptyState'; import ScheduleMigrationModal from '../ScheduleMigrationModal/ScheduleMigrationModal'; @@ -135,6 +135,14 @@ class MigrationsNotStartedList extends React.Component { planId={plan.id} planName={plan.name} /> + { + e.stopPropagation(); + alert('TODO: open plan wizard in edit mode'); + }} + > + {__('Edit')} + From 74595491e930a8e9d8f131437326277c7554362f Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 30 Aug 2018 17:40:17 -0400 Subject: [PATCH 02/27] Add action and handler for opening plan wizard in edit mode --- .../react/screens/App/Overview/Overview.js | 5 ++++- .../screens/App/Overview/OverviewActions.js | 10 +++++++++- .../screens/App/Overview/OverviewConstants.js | 1 + .../screens/App/Overview/OverviewReducer.js | 20 ++++++++++++++++--- .../components/Migrations/Migrations.js | 7 +++++-- .../Migrations/MigrationsNotStartedList.js | 8 +++++--- .../Overview/screens/PlanWizard/PlanWizard.js | 10 ++++++++-- .../screens/PlanWizard/PlanWizardSelectors.js | 3 ++- 8 files changed, 51 insertions(+), 13 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/Overview.js b/app/javascript/react/screens/App/Overview/Overview.js index 101ca2eeef..d08fff1054 100644 --- a/app/javascript/react/screens/App/Overview/Overview.js +++ b/app/javascript/react/screens/App/Overview/Overview.js @@ -254,7 +254,8 @@ class Overview extends React.Component { scheduleMigrationModal, scheduleMigrationPlan, scheduleMigration, - plansMutatedWithMappingInfo + plansMutatedWithMappingInfo, + showPlanWizardEditModeAction } = this.props; const inProgressRequestsTransformationMappings = () => { @@ -348,6 +349,7 @@ class Overview extends React.Component { scheduleMigrationPlan={scheduleMigrationPlan} scheduleMigration={scheduleMigration} plansMutatedWithMappingInfo={plansMutatedWithMappingInfo} + showPlanWizardEditModeAction={showPlanWizardEditModeAction} /> )} {hasSufficientProviders ? ( @@ -432,6 +434,7 @@ Overview.propTypes = { isContinuingToPlan: PropTypes.bool, planWizardId: PropTypes.string, continueToPlanAction: PropTypes.func, + showPlanWizardEditModeAction: PropTypes.func, shouldReloadMappings: PropTypes.bool, fetchClustersAction: PropTypes.func, fetchClustersUrl: PropTypes.string, diff --git a/app/javascript/react/screens/App/Overview/OverviewActions.js b/app/javascript/react/screens/App/Overview/OverviewActions.js index f1fc42eb8f..b8a2f9773d 100644 --- a/app/javascript/react/screens/App/Overview/OverviewActions.js +++ b/app/javascript/react/screens/App/Overview/OverviewActions.js @@ -31,7 +31,8 @@ import { V2V_SCHEDULE_MIGRATION, V2V_SET_MIGRATIONS_FILTER, V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL, - YES_TO_DELETE_AND_HIDE_DELETE_CONFIRMATION_MODAL + YES_TO_DELETE_AND_HIDE_DELETE_CONFIRMATION_MODAL, + SHOW_PLAN_WIZARD_EDIT_MODE } from './OverviewConstants'; export const showConfirmModalAction = modalOptions => ({ @@ -56,6 +57,13 @@ export const showPlanWizardAction = id => dispatch => { }); }; +export const showPlanWizardEditModeAction = id => dispatch => { + dispatch({ + type: SHOW_PLAN_WIZARD_EDIT_MODE, + editingPlanId: id + }); +}; + export const fetchProvidersAction = () => dispatch => { dispatch({ type: FETCH_PROVIDERS, diff --git a/app/javascript/react/screens/App/Overview/OverviewConstants.js b/app/javascript/react/screens/App/Overview/OverviewConstants.js index a46cd3a77b..08d4b8ad29 100644 --- a/app/javascript/react/screens/App/Overview/OverviewConstants.js +++ b/app/javascript/react/screens/App/Overview/OverviewConstants.js @@ -3,6 +3,7 @@ export const SHOW_MAPPING_WIZARD = 'SHOW_MAPPING_WIZARD'; export const HIDE_MAPPING_WIZARD = 'HIDE_MAPPING_WIZARD'; export const MAPPING_WIZARD_EXITED = 'MAPPING_WIZARD_EXITED'; export const SHOW_PLAN_WIZARD = 'SHOW_PLAN_WIZARD'; +export const SHOW_PLAN_WIZARD_EDIT_MODE = 'SHOW_PLAN_WIZARD_EDIT_MODE'; export const HIDE_PLAN_WIZARD = 'HIDE_PLAN_WIZARD'; export const PLAN_WIZARD_EXITED = 'PLAN_WIZARD_EXITED'; export const PLAN_WIZARD_NEXT = 'PLAN_WIZARD_NEXT'; diff --git a/app/javascript/react/screens/App/Overview/OverviewReducer.js b/app/javascript/react/screens/App/Overview/OverviewReducer.js index 1c8ba921f2..38b417bb6a 100644 --- a/app/javascript/react/screens/App/Overview/OverviewReducer.js +++ b/app/javascript/react/screens/App/Overview/OverviewReducer.js @@ -42,7 +42,8 @@ import { FETCH_NETWORKS, FETCH_DATASTORES, V2V_TOGGLE_SCHEDULE_MIGRATION_MODAL, - V2V_SCHEDULE_MIGRATION + V2V_SCHEDULE_MIGRATION, + SHOW_PLAN_WIZARD_EDIT_MODE } from './OverviewConstants'; import { planTransmutation, sufficientProviders } from './helpers'; @@ -52,7 +53,8 @@ export const initialState = Immutable({ hideMappingWizard: true, planWizardVisible: false, hidePlanWizard: true, - planWizardId: null, + planWizardId: null, // id of infrastructure mapping to use for new plan + editingPlanId: null, // id of migration plan to edit hasSufficientProviders: false, isRejectedProviders: false, isFetchingProviders: false, @@ -138,8 +140,20 @@ export default (state = initialState, action) => { planWizardId: (payload && payload.id) || null }); } + case SHOW_PLAN_WIZARD_EDIT_MODE: { + const { editingPlanId } = action; + return Immutable.merge(state, { + planWizardVisible: true, + hidePlanWizard: false, + planWizardId: null, + editingPlanId + }); + } case HIDE_PLAN_WIZARD: - return state.set('hidePlanWizard', true).set('planWizardId', null); + return state + .set('hidePlanWizard', true) + .set('planWizardId', null) + .set('editingPlanId', null); case PLAN_WIZARD_EXITED: return state.set('planWizardVisible', false); case `${FETCH_PROVIDERS}_PENDING`: diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/Migrations.js b/app/javascript/react/screens/App/Overview/components/Migrations/Migrations.js index 60d222b798..5c9cc8d077 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/Migrations.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/Migrations.js @@ -39,7 +39,8 @@ const Migrations = ({ scheduleMigrationModal, scheduleMigrationPlan, scheduleMigration, - plansMutatedWithMappingInfo + plansMutatedWithMappingInfo, + showPlanWizardEditModeAction }) => { const filterOptions = [ MIGRATIONS_FILTERS.notStarted, @@ -125,6 +126,7 @@ const Migrations = ({ plansMutatedWithMappingInfo={plansMutatedWithMappingInfo} deleteTransformationPlanAction={deleteTransformationPlanAction} deleteTransformationPlanUrl={deleteTransformationPlanUrl} + showPlanWizardEditModeAction={showPlanWizardEditModeAction} /> )} {activeFilter === MIGRATIONS_FILTERS.inProgress && ( @@ -216,7 +218,8 @@ Migrations.propTypes = { scheduleMigrationModal: PropTypes.bool, scheduleMigrationPlan: PropTypes.object, scheduleMigration: PropTypes.func, - plansMutatedWithMappingInfo: PropTypes.bool + plansMutatedWithMappingInfo: PropTypes.bool, + showPlanWizardEditModeAction: PropTypes.func }; Migrations.defaultProps = { transformationPlans: [], diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js index 53cce3d920..02553861bb 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js @@ -61,7 +61,8 @@ class MigrationsNotStartedList extends React.Component { fetchTransformationPlansUrl, plansMutatedWithMappingInfo, deleteTransformationPlanAction, - deleteTransformationPlanUrl + deleteTransformationPlanUrl, + showPlanWizardEditModeAction } = this.props; const sortedMigrations = this.sortedMigrations(); @@ -138,7 +139,7 @@ class MigrationsNotStartedList extends React.Component { { e.stopPropagation(); - alert('TODO: open plan wizard in edit mode'); + showPlanWizardEditModeAction(plan.id); }} > {__('Edit')} @@ -236,7 +237,8 @@ MigrationsNotStartedList.propTypes = { fetchTransformationPlansUrl: PropTypes.string, plansMutatedWithMappingInfo: PropTypes.bool, deleteTransformationPlanAction: PropTypes.func, - deleteTransformationPlanUrl: PropTypes.string + deleteTransformationPlanUrl: PropTypes.string, + showPlanWizardEditModeAction: PropTypes.func }; MigrationsNotStartedList.defaultProps = { migrateClick: noop, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index 9b2ed21634..ac39f46534 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -185,9 +185,14 @@ class PlanWizard extends React.Component { planWizardExitedAction, alertText, alertType, - hideAlertAction + hideAlertAction, + editingPlan } = this.props; + // TODO [mturley] remove me: + if (editingPlan) console.log('TODO: edit this plan: ', editingPlan); + if (!editingPlan) console.log('Not editing a plan'); + const wizardSteps = this.getWizardSteps(); const { activeStepIndex, plansBody } = this.state; @@ -268,7 +273,8 @@ PlanWizard.propTypes = { alertText: PropTypes.string, alertType: PropTypes.string, setMetadataWithBackButtonClickedAction: PropTypes.func, - setMetadataWithNextButtonClickedAction: PropTypes.func + setMetadataWithNextButtonClickedAction: PropTypes.func, + editingPlan: PropTypes.object }; PlanWizard.defaultProps = { hidePlanWizard: true, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js index 96d3b751df..bda884d1c4 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js @@ -1,6 +1,7 @@ export const planWizardOverviewFilter = overview => ({ hidePlanWizard: overview.hidePlanWizard, - transformationMappings: overview.transformationMappings + transformationMappings: overview.transformationMappings, + editingPlan: overview.transformationPlans.find(plan => plan.id === overview.editingPlanId) }); export const planWizardFormFilter = form => ({ From 31d52f50b89321685010100048750eeea4b902bd Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 31 Aug 2018 10:58:45 -0400 Subject: [PATCH 03/27] Prefill general step and add exception to duplicate name validation --- .../__test__/__snapshots__/index.test.js.snap | 1 + .../screens/PlanWizard/PlanWizardSelectors.js | 5 ++- .../__snapshots__/index.test.js.snap | 1 + .../PlanWizardGeneralStep/helpers.js | 5 +-- .../components/PlanWizardGeneralStep/index.js | 31 +++++++++++++------ .../PlanWizardVMStep/PlanWizardVMStep.js | 6 ++-- .../components/PlanWizardVMStep/index.js | 6 ++-- 7 files changed, 39 insertions(+), 16 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/__test__/__snapshots__/index.test.js.snap b/app/javascript/react/screens/App/Overview/__test__/__snapshots__/index.test.js.snap index 1581b35bfd..fbacc63401 100644 --- a/app/javascript/react/screens/App/Overview/__test__/__snapshots__/index.test.js.snap +++ b/app/javascript/react/screens/App/Overview/__test__/__snapshots__/index.test.js.snap @@ -61,6 +61,7 @@ Object { "showDeleteConfirmationModalAction": [Function], "showMappingWizardAction": [Function], "showPlanWizardAction": [Function], + "showPlanWizardEditModeAction": [Function], "toggleScheduleMigrationModal": [Function], "transformationMappings": Array [], "transformationPlans": Array [], diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js index bda884d1c4..7198221508 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardSelectors.js @@ -1,7 +1,10 @@ +export const findEditingPlan = (transformationPlans, editingPlanId) => + editingPlanId && transformationPlans.find(plan => plan.id === editingPlanId); + export const planWizardOverviewFilter = overview => ({ hidePlanWizard: overview.hidePlanWizard, transformationMappings: overview.transformationMappings, - editingPlan: overview.transformationPlans.find(plan => plan.id === overview.editingPlanId) + editingPlan: findEditingPlan(overview.transformationPlans, overview.editingPlanId) }); export const planWizardFormFilter = form => ({ diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/__tests__/__snapshots__/index.test.js.snap b/app/javascript/react/screens/App/Overview/screens/PlanWizard/__tests__/__snapshots__/index.test.js.snap index 58d98f665b..430cd675ad 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/__tests__/__snapshots__/index.test.js.snap +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/__tests__/__snapshots__/index.test.js.snap @@ -4,6 +4,7 @@ exports[`Plan Wizard integration test should mount the PlanWizard with mapStateT Object { "alertText": undefined, "alertType": undefined, + "editingPlan": undefined, "hideAlertAction": [Function], "hideConfirmModalAction": [Function], "hidePlanWizard": false, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/helpers.js index 59c7b8a475..1ede6b26ea 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/helpers.js @@ -1,7 +1,7 @@ export const asyncValidate = (values, dispatch, props) => new Promise((resolve, reject) => { const { name: newPlanName } = values; - const { transformationPlans, archivedTransformationPlans } = props; + const { transformationPlans, archivedTransformationPlans, editingPlan } = props; const existingTransformationPlanNames = transformationPlans.reduce( (names, plan) => [...names, plan.name.trim()], [] @@ -13,8 +13,9 @@ export const asyncValidate = (values, dispatch, props) => const allPlanNames = [...existingTransformationPlanNames, ...existingArchivedPlanNames]; const duplicateName = allPlanNames.find(existingPlanName => existingPlanName === newPlanName.trim()); + const duplicateIsEditingPlanName = editingPlan && duplicateName === editingPlan.name; - if (duplicateName) { + if (duplicateName && !duplicateIsEditingPlanName) { props.showAlertAction(sprintf(__('Name %s already exists'), newPlanName)); const error = { name: 'Please enter a unique name' }; reject(error); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js index f8c34e84d7..bdf6738215 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js @@ -1,16 +1,29 @@ import { connect } from 'react-redux'; import PlanWizardGeneralStep from './PlanWizardGeneralStep'; import { showAlertAction, hideAlertAction } from '../../PlanWizardActions'; +import { findEditingPlan } from '../../PlanWizardSelectors'; -const mapStateToProps = ({ overview }) => ({ - transformationMappings: overview.transformationMappings, - transformationPlans: overview.transformationPlans, - archivedTransformationPlans: overview.archivedTransformationPlans, - initialValues: { - infrastructure_mapping: overview.planWizardId, - vm_choice_radio: 'vms_via_discovery' - } -}); +const mapStateToProps = ({ overview }) => { + const editingPlan = findEditingPlan(overview.transformationPlans, overview.editingPlanId); + const prefilledMappingId = + editingPlan && + editingPlan.options && + editingPlan.options.config_info && + editingPlan.options.config_info.transformation_mapping_id; + return { + transformationMappings: overview.transformationMappings, + transformationPlans: overview.transformationPlans, + archivedTransformationPlans: overview.archivedTransformationPlans, + initialValues: { + infrastructure_mapping: prefilledMappingId || overview.planWizardId, + vm_choice_radio: 'vms_via_discovery', + name: editingPlan ? editingPlan.name : '', + description: editingPlan ? editingPlan.description : '' + }, + enableReinitialize: true, // Tells redux-form to use new initialValues when they change + editingPlan + }; +}; const actions = { showAlertAction, hideAlertAction }; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 450df60382..f7ba28f257 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -65,7 +65,8 @@ class PlanWizardVMStep extends React.Component { invalid_vms, conflict_vms, validationServiceCalled, - csvImportAction + csvImportAction, + editingPlan } = this.props; const discoveryMode = vm_choice_radio === 'vms_via_discovery'; @@ -187,7 +188,8 @@ PlanWizardVMStep.propTypes = { errorValidatingVms: PropTypes.string, // eslint-disable-line react/no-unused-prop-types valid_vms: PropTypes.array, invalid_vms: PropTypes.array, - conflict_vms: PropTypes.array + conflict_vms: PropTypes.array, + editingPlan: PropTypes.object }; PlanWizardVMStep.defaultProps = { diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/index.js index 80a48eea39..0f03e00959 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/index.js @@ -3,14 +3,16 @@ import PlanWizardVMStep from './PlanWizardVMStep'; import * as PlanWizardVMStepActions from './PlanWizardVMStepActions'; import reducer from './PlanWizardVMStepReducer'; +import { findEditingPlan } from '../../PlanWizardSelectors'; export const reducers = { planWizardVMStep: reducer }; -const mapStateToProps = ({ planWizardVMStep, form }, ownProps) => ({ +const mapStateToProps = ({ planWizardVMStep, form, overview }, ownProps) => ({ ...planWizardVMStep, ...ownProps.data, vm_choice_radio: form.planWizardGeneralStep.values.vm_choice_radio, - infrastructure_mapping_id: form.planWizardGeneralStep.values.infrastructure_mapping + infrastructure_mapping_id: form.planWizardGeneralStep.values.infrastructure_mapping, + editingPlan: findEditingPlan(overview.transformationPlans, overview.editingPlanId) }); export default connect( From 0419032f1ea9fb05c5ac91287683c23aa4fc6825 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 4 Sep 2018 11:17:50 -0400 Subject: [PATCH 04/27] Pre-select VMs in discovery mode, query for extra details (all except path) --- .../PlanWizardVMStep/PlanWizardVMStep.js | 43 ++++++++++++++++--- .../PlanWizardVMStepActions.js | 22 +++++++++- .../PlanWizardVMStepConstants.js | 1 + .../PlanWizardVMStepReducer.js | 26 ++++++++++- .../components/PlanWizardVMStep/helpers.js | 7 +++ 5 files changed, 90 insertions(+), 9 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index f7ba28f257..338bc52d0a 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -4,15 +4,22 @@ import Immutable from 'seamless-immutable'; import { Field, reduxForm } from 'redux-form'; import { length } from 'redux-form-validators'; import { Button, Icon } from 'patternfly-react'; +import numeral from 'numeral'; import PlanWizardVMStepTable from './components/PlanWizardVMStepTable'; import CSVDropzoneField from './components/CSVDropzoneField'; +import { getVmIds } from './helpers'; +import { V2V_VM_POST_VALIDATION_REASONS } from './PlanWizardVMStepConstants'; class PlanWizardVMStep extends React.Component { componentDidMount() { - const { vm_choice_radio } = this.props; + const { vm_choice_radio, editingPlan, queryPrefilledVmsAction } = this.props; if (vm_choice_radio === 'vms_via_discovery') { this.validateVms(); } + if (editingPlan) { + const vmIds = getVmIds(editingPlan); + queryPrefilledVmsAction(vmIds); + } } componentDidUpdate(prevProps) { const { vm_choice_radio } = this.props; @@ -54,6 +61,23 @@ class PlanWizardVMStep extends React.Component { } }); }; + getPreselectedVmsForEditing = () => { + const { editingPlan, vmsQueryResults } = this.props; + const vmIds = getVmIds(editingPlan); + return vmIds.map(vmId => { + const result = vmsQueryResults.find(res => res.id === vmId); + return { + id: vmId, + name: result ? result.name : '', + cluster: result ? result.ems_cluster.name : '', + path: '', // TODO [mturley] how can we fetch the path? + allocated_size: result ? numeral(result.allocated_disk_storage).format('0.00b') : '', + selected: true, + valid: true, + reason: V2V_VM_POST_VALIDATION_REASONS.ok + }; + }); + }; render() { const { vm_choice_radio, @@ -66,7 +90,8 @@ class PlanWizardVMStep extends React.Component { conflict_vms, validationServiceCalled, csvImportAction, - editingPlan + editingPlan, + isQueryingVms } = this.props; const discoveryMode = vm_choice_radio === 'vms_via_discovery'; @@ -122,7 +147,7 @@ class PlanWizardVMStep extends React.Component { ); } else if (!discoveryMode && (valid_vms.length === 0 && invalid_vms.length === 0 && conflict_vms.length === 0)) { return ; - } else if (!isValidatingVms && validationServiceCalled) { + } else if (!isValidatingVms && validationServiceCalled && !(editingPlan && isQueryingVms)) { // set make rows editable so they can be selected const validVms = Immutable.asMutable(valid_vms, { deep: true }); const inValidsVms = Immutable.asMutable(invalid_vms, { deep: true }).concat( @@ -132,7 +157,8 @@ class PlanWizardVMStep extends React.Component { const validVmsWithSelections = discoveryMode ? validVms : validVms.filter(vm => vm.valid === true).map(vm => ({ ...vm, selected: true })); - const combined = [...inValidsVms, ...conflictVms, ...validVmsWithSelections]; + const preselectedVms = this.getPreselectedVmsForEditing(); + const combined = [...preselectedVms, ...inValidsVms, ...conflictVms, ...validVmsWithSelections]; if (combined.length) { return ( @@ -141,7 +167,9 @@ class PlanWizardVMStep extends React.Component { name="selectedVms" component={PlanWizardVMStepTable} rows={combined} - initialSelectedRows={discoveryMode ? [] : validVmsWithSelections.map(r => r.id)} + initialSelectedRows={ + discoveryMode ? preselectedVms.map(r => r.id) : validVmsWithSelections.map(r => r.id) + } onCsvImportAction={this.showOverwriteCsvConfirmModal} discoveryMode={discoveryMode} validate={[ @@ -189,7 +217,10 @@ PlanWizardVMStep.propTypes = { valid_vms: PropTypes.array, invalid_vms: PropTypes.array, conflict_vms: PropTypes.array, - editingPlan: PropTypes.object + editingPlan: PropTypes.object, + queryPrefilledVmsAction: PropTypes.func, + isQueryingVms: PropTypes.bool, + vmsQueryResults: PropTypes.array }; PlanWizardVMStep.defaultProps = { diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js index d81b4b45bf..c503561a80 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js @@ -1,7 +1,7 @@ import { reset } from 'redux-form'; import URI from 'urijs'; import API from '../../../../../../../../common/API'; -import { V2V_VM_STEP_RESET, V2V_VALIDATE_VMS } from './PlanWizardVMStepConstants'; +import { V2V_VM_STEP_RESET, V2V_VALIDATE_VMS, QUERY_V2V_PLAN_VMS } from './PlanWizardVMStepConstants'; export { showConfirmModalAction, hideConfirmModalAction } from '../../../../OverviewActions'; @@ -43,3 +43,23 @@ export const csvParseErrorAction = errMsg => dispatch => { payload: errMsg }); }; + +const _queryPrefilledVmsActionCreator = ids => dispatch => { + const resources = ids.map(id => ({ + id + })); + + return dispatch({ + type: QUERY_V2V_PLAN_VMS, + payload: API.post('/api/vms?expand=resources&attributes=name,ems_cluster.name,allocated_disk_storage', { + action: 'query', + resources + }) + }); +}; + +export const queryPrefilledVmsAction = ids => _queryPrefilledVmsActionCreator(ids); + +// TODO [mturley] format allocated_disk_storage with numeral.js, +// TODO [mturley] figure out details of getting the path of a VM +// TODO [mturley] talk to Aparna about this diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepConstants.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepConstants.js index e6857f1705..daab056e46 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepConstants.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepConstants.js @@ -1,5 +1,6 @@ export const V2V_VALIDATE_VMS = 'V2V_VALIDATE_VMS'; export const V2V_VM_STEP_RESET = 'V2V_VM_STEP_RESET'; +export const QUERY_V2V_PLAN_VMS = 'QUERY_V2V_PLAN_VMS'; export const V2V_VM_POST_VALIDATION_REASONS = { conflict: __('VM is in conflict'), diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js index 341197321a..5f6a727514 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js @@ -1,6 +1,6 @@ import Immutable from 'seamless-immutable'; -import { V2V_VALIDATE_VMS, V2V_VM_STEP_RESET } from './PlanWizardVMStepConstants'; +import { V2V_VALIDATE_VMS, V2V_VM_STEP_RESET, QUERY_V2V_PLAN_VMS } from './PlanWizardVMStepConstants'; import { _formatConflictVms, _formatInvalidVms, _formatValidVms } from './helpers'; const initialState = Immutable({ @@ -10,7 +10,11 @@ const initialState = Immutable({ errorValidatingVms: null, valid_vms: [], invalid_vms: [], - conflict_vms: [] + conflict_vms: [], + isQueryingVms: false, + isVmsQueryRejected: false, + vmsQueryError: null, + vmsQueryResults: [] }); export default (state = initialState, action) => { @@ -47,6 +51,24 @@ export default (state = initialState, action) => { .set('isRejectedValidatingVms', true) .set('isCSVParseError', true) .set('isValidatingVms', false); + case `${QUERY_V2V_PLAN_VMS}_PENDING`: + return state + .set('isQueryingVms', true) + .set('isVmsQueryRejected', false) + .set('vmsQueryError', null) + .set('vmsQueryResults', []); + case `${QUERY_V2V_PLAN_VMS}_FULFILLED`: + return state + .set('isQueryingVms', false) + .set('isVmsQueryRejected', false) + .set('vmsQueryError', null) + .set('vmsQueryResults', action.payload.data.results); + case `${QUERY_V2V_PLAN_VMS}_REJECTED`: + return state + .set('isQueryingVms', false) + .set('isVmsQueryRejected', true) + .set('vmsQueryError', action.payload) + .set('vmsQueryResults', []); case V2V_VM_STEP_RESET: return state .set('validationServiceCalled', false) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js index 985812a46d..23e2c4620f 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js @@ -93,3 +93,10 @@ export const _formatConflictVms = vms => { }) ); }; + +export const getVmIds = editingPlan => + editingPlan && + editingPlan.options && + editingPlan.options.config_info && + editingPlan.options.config_info.actions && + editingPlan.options.config_info.actions.map(action => action.vm_id); From 2ed5d86fa09b645069c91b8c068baf7e17f3cb03 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 7 Sep 2018 12:45:15 -0400 Subject: [PATCH 05/27] Fix error in VM step when creating new plan --- .../PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 338bc52d0a..700172c470 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -63,6 +63,7 @@ class PlanWizardVMStep extends React.Component { }; getPreselectedVmsForEditing = () => { const { editingPlan, vmsQueryResults } = this.props; + if (!editingPlan) return []; const vmIds = getVmIds(editingPlan); return vmIds.map(vmId => { const result = vmsQueryResults.find(res => res.id === vmId); From f501b62f7c6727558aaa7a4cd338e36e4602c699 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 7 Sep 2018 16:36:34 -0400 Subject: [PATCH 06/27] Format preselected_vms rows in reducer --- .../PlanWizardVMStep/PlanWizardVMStep.js | 39 +++++-------------- .../PlanWizardVMStepActions.js | 4 +- .../PlanWizardVMStepReducer.js | 24 ++++++++---- .../components/PlanWizardVMStep/helpers.js | 12 ++++++ 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 700172c470..7c169dd094 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -4,21 +4,19 @@ import Immutable from 'seamless-immutable'; import { Field, reduxForm } from 'redux-form'; import { length } from 'redux-form-validators'; import { Button, Icon } from 'patternfly-react'; -import numeral from 'numeral'; import PlanWizardVMStepTable from './components/PlanWizardVMStepTable'; import CSVDropzoneField from './components/CSVDropzoneField'; import { getVmIds } from './helpers'; -import { V2V_VM_POST_VALIDATION_REASONS } from './PlanWizardVMStepConstants'; class PlanWizardVMStep extends React.Component { componentDidMount() { - const { vm_choice_radio, editingPlan, queryPrefilledVmsAction } = this.props; + const { vm_choice_radio, editingPlan, queryPreselectedVmsAction } = this.props; if (vm_choice_radio === 'vms_via_discovery') { this.validateVms(); } if (editingPlan) { const vmIds = getVmIds(editingPlan); - queryPrefilledVmsAction(vmIds); + queryPreselectedVmsAction(vmIds); } } componentDidUpdate(prevProps) { @@ -61,24 +59,6 @@ class PlanWizardVMStep extends React.Component { } }); }; - getPreselectedVmsForEditing = () => { - const { editingPlan, vmsQueryResults } = this.props; - if (!editingPlan) return []; - const vmIds = getVmIds(editingPlan); - return vmIds.map(vmId => { - const result = vmsQueryResults.find(res => res.id === vmId); - return { - id: vmId, - name: result ? result.name : '', - cluster: result ? result.ems_cluster.name : '', - path: '', // TODO [mturley] how can we fetch the path? - allocated_size: result ? numeral(result.allocated_disk_storage).format('0.00b') : '', - selected: true, - valid: true, - reason: V2V_VM_POST_VALIDATION_REASONS.ok - }; - }); - }; render() { const { vm_choice_radio, @@ -89,6 +69,7 @@ class PlanWizardVMStep extends React.Component { valid_vms, invalid_vms, conflict_vms, + preselected_vms, validationServiceCalled, csvImportAction, editingPlan, @@ -158,8 +139,7 @@ class PlanWizardVMStep extends React.Component { const validVmsWithSelections = discoveryMode ? validVms : validVms.filter(vm => vm.valid === true).map(vm => ({ ...vm, selected: true })); - const preselectedVms = this.getPreselectedVmsForEditing(); - const combined = [...preselectedVms, ...inValidsVms, ...conflictVms, ...validVmsWithSelections]; + const combined = [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsWithSelections]; if (combined.length) { return ( @@ -169,7 +149,7 @@ class PlanWizardVMStep extends React.Component { component={PlanWizardVMStepTable} rows={combined} initialSelectedRows={ - discoveryMode ? preselectedVms.map(r => r.id) : validVmsWithSelections.map(r => r.id) + discoveryMode ? preselected_vms.map(r => r.id) : validVmsWithSelections.map(r => r.id) } onCsvImportAction={this.showOverwriteCsvConfirmModal} discoveryMode={discoveryMode} @@ -218,10 +198,10 @@ PlanWizardVMStep.propTypes = { valid_vms: PropTypes.array, invalid_vms: PropTypes.array, conflict_vms: PropTypes.array, + preselected_vms: PropTypes.array, editingPlan: PropTypes.object, - queryPrefilledVmsAction: PropTypes.func, - isQueryingVms: PropTypes.bool, - vmsQueryResults: PropTypes.array + queryPreselectedVmsAction: PropTypes.func, + isQueryingVms: PropTypes.bool }; PlanWizardVMStep.defaultProps = { @@ -235,7 +215,8 @@ PlanWizardVMStep.defaultProps = { errorValidatingVms: null, valid_vms: [], invalid_vms: [], - conflict_vms: [] + conflict_vms: [], + preselected_vms: [] }; export default reduxForm({ diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js index c503561a80..bc76a7e233 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js @@ -44,7 +44,7 @@ export const csvParseErrorAction = errMsg => dispatch => { }); }; -const _queryPrefilledVmsActionCreator = ids => dispatch => { +const _queryPreselectedVmsActionCreator = ids => dispatch => { const resources = ids.map(id => ({ id })); @@ -58,7 +58,7 @@ const _queryPrefilledVmsActionCreator = ids => dispatch => { }); }; -export const queryPrefilledVmsAction = ids => _queryPrefilledVmsActionCreator(ids); +export const queryPreselectedVmsAction = ids => _queryPreselectedVmsActionCreator(ids); // TODO [mturley] format allocated_disk_storage with numeral.js, // TODO [mturley] figure out details of getting the path of a VM diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js index 5f6a727514..38fd990663 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepReducer.js @@ -1,7 +1,7 @@ import Immutable from 'seamless-immutable'; import { V2V_VALIDATE_VMS, V2V_VM_STEP_RESET, QUERY_V2V_PLAN_VMS } from './PlanWizardVMStepConstants'; -import { _formatConflictVms, _formatInvalidVms, _formatValidVms } from './helpers'; +import { _formatConflictVms, _formatInvalidVms, _formatValidVms, _formatPreselectedVms } from './helpers'; const initialState = Immutable({ isValidatingVms: false, @@ -11,10 +11,10 @@ const initialState = Immutable({ valid_vms: [], invalid_vms: [], conflict_vms: [], + preselected_vms: [], isQueryingVms: false, isVmsQueryRejected: false, - vmsQueryError: null, - vmsQueryResults: [] + vmsQueryError: null }); export default (state = initialState, action) => { @@ -56,25 +56,35 @@ export default (state = initialState, action) => { .set('isQueryingVms', true) .set('isVmsQueryRejected', false) .set('vmsQueryError', null) - .set('vmsQueryResults', []); - case `${QUERY_V2V_PLAN_VMS}_FULFILLED`: + .set('preselected_vms', []); + case `${QUERY_V2V_PLAN_VMS}_FULFILLED`: { + const { payload } = action; + if (payload && payload.data) { + return state + .set('isQueryingVms', false) + .set('isVmsQueryRejected', false) + .set('vmsQueryError', null) + .set('preselected_vms', _formatPreselectedVms(action.payload.data.results)); + } return state .set('isQueryingVms', false) .set('isVmsQueryRejected', false) .set('vmsQueryError', null) - .set('vmsQueryResults', action.payload.data.results); + .set('preselected_vms', []); + } case `${QUERY_V2V_PLAN_VMS}_REJECTED`: return state .set('isQueryingVms', false) .set('isVmsQueryRejected', true) .set('vmsQueryError', action.payload) - .set('vmsQueryResults', []); + .set('preselected_vms', []); case V2V_VM_STEP_RESET: return state .set('validationServiceCalled', false) .set('valid_vms', []) .set('invalid_vms', []) .set('conflict_vms', []) + .set('preselected_vms', []) .set('isRejectedValidatingVms', false) .set('isValidatingVms', false); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js index 23e2c4620f..7b1a323203 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js @@ -100,3 +100,15 @@ export const getVmIds = editingPlan => editingPlan.options.config_info && editingPlan.options.config_info.actions && editingPlan.options.config_info.actions.map(action => action.vm_id); + +export const _formatPreselectedVms = vmsQueryResults => + vmsQueryResults.map(result => ({ + id: result.id, + name: result.name, + cluster: result.ems_cluster.name, + path: '', // TODO [mturley] how can we fetch the path? + allocated_size: numeral(result.allocated_disk_storage).format('0.00b'), + selected: true, + valid: true, + reason: V2V_VM_POST_VALIDATION_REASONS.ok + })); From b91ec89f7151abb742bc96f5477336e1accd57c6 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 11:28:51 -0400 Subject: [PATCH 07/27] Carry over selected VMs properly to Advanced Options step --- .../components/PlanWizardAdvancedOptionsStep/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js index 5e2b850294..45f6f964f6 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js @@ -26,7 +26,7 @@ const mapStateToProps = ( const allVms = vm_choice_radio === 'vms_via_csv' ? [...planWizardVMStep.valid_vms, ...planWizardVMStep.invalid_vms, ...planWizardVMStep.conflict_vms] - : planWizardVMStep.valid_vms; + : [...planWizardVMStep.preselected_vms, ...planWizardVMStep.valid_vms]; return { ...planWizardAdvancedOptionsStep, From 8d8b60dffe3d9e0b09722fdc5265ddbb5f78dbe6 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 12:50:15 -0400 Subject: [PATCH 08/27] Pre-fill selections in Advanced Options step --- .../PlanWizardAdvancedOptionsStep.js | 19 +++++++++--------- .../PlanWizardAdvancedOptionsStepSelectors.js | 1 - .../PlanWizardAdvancedOptionsStep/helpers.js | 20 +++++++++++++++++++ .../PlanWizardAdvancedOptionsStep/index.js | 19 ++++++++++++++++-- .../PlanWizardInstancePropertiesStep/index.js | 2 +- .../components/PlanWizardVMStep/helpers.js | 4 ++-- 6 files changed, 49 insertions(+), 16 deletions(-) delete mode 100644 app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStepSelectors.js create mode 100644 app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js index e7f7f803b7..f3395fcfa2 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js @@ -5,13 +5,18 @@ import { Form, Spinner } from 'patternfly-react'; import PlanWizardAdvancedOptionsStepTable from './components/PlanWizardAdvancedOptionsStepTable/PlanWizardAdvancedOptionsStepTable'; import { BootstrapSelect } from '../../../../../common/forms/BootstrapSelect'; +import { preselectPlaybooksForVms } from './helpers'; class PlanWizardAdvancedOptionsStep extends Component { constructor(props) { super(props); if (props.vms.length === 0) { - props.setVmsAction(props.vmStepSelectedVms); + if (!props.editingPlan) { + props.setVmsAction(props.vmStepSelectedVms); + } else { + props.setVmsAction(preselectPlaybooksForVms(props.editingPlan, props.vmStepSelectedVms)); + } } } @@ -90,7 +95,8 @@ PlanWizardAdvancedOptionsStep.propTypes = { vms: PropTypes.array, setVmsAction: PropTypes.func, vmStepSelectedVms: PropTypes.array, - change: PropTypes.func + change: PropTypes.func, + editingPlan: PropTypes.object }; PlanWizardAdvancedOptionsStep.defaultProps = { @@ -99,13 +105,6 @@ PlanWizardAdvancedOptionsStep.defaultProps = { export default reduxForm({ form: 'planWizardAdvancedOptionsStep', - initialValues: { - playbookVms: { - preMigration: [], - postMigration: [] - }, - preMigrationPlaybook: '', - postMigrationPlaybook: '' - }, destroyOnUnmount: false + // initialValues prop is passed in via ./index })(PlanWizardAdvancedOptionsStep); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStepSelectors.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStepSelectors.js deleted file mode 100644 index 25f6d21251..0000000000 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStepSelectors.js +++ /dev/null @@ -1 +0,0 @@ -export const getVMStepSelectedVms = (allVms, selectedVms) => allVms.filter(vm => selectedVms.includes(vm.id)); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js new file mode 100644 index 0000000000..1081b165f8 --- /dev/null +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js @@ -0,0 +1,20 @@ +export const getVMStepSelectedVms = (allVms, selectedVms) => allVms.filter(vm => selectedVms.includes(vm.id)); + +// Property can be 'pre_service' or 'post_service'. +// Returns an array of vm ids which have a truthy value for that property in the plan being edited. +export const getVmIdsWithProperty = (editingPlan, property) => { + const actions = editingPlan && editingPlan.options && editingPlan.options.config_info && editingPlan.options.config_info.actions; + if (!actions) return []; + const actionsWithProperty = actions.filter(action => action[property]); + return actionsWithProperty.map(action => action.vm_id); +}; + +export const preselectPlaybooksForVms = (editingPlan, vms) => { + const vmIdsWithPreService = getVmIdsWithProperty(editingPlan, 'pre_service'); + const vmIdsWithPostService = getVmIdsWithProperty(editingPlan, 'post_service'); + return vms.map(vm => ({ + ...vm, + preMigration: vmIdsWithPreService.some(id => id === vm.id), + postMigration: vmIdsWithPostService.some(id => id === vm.id) + })); +}; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js index 45f6f964f6..334c3c9549 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js @@ -2,8 +2,9 @@ import { connect } from 'react-redux'; import PlanWizardAdvancedOptionsStep from './PlanWizardAdvancedOptionsStep'; import * as PlanWizardAdvancedOptionsStepActions from './PlanWizardAdvancedOptionsStepActions'; -import { getVMStepSelectedVms } from './PlanWizardAdvancedOptionsStepSelectors'; +import { getVMStepSelectedVms, getVmIdsWithProperty } from './helpers'; import reducer from './PlanWizardAdvancedOptionsStepReducer'; +import { findEditingPlan } from '../../PlanWizardSelectors'; export const reducers = { planWizardAdvancedOptionsStep: reducer }; @@ -11,6 +12,7 @@ const mapStateToProps = ( { planWizardAdvancedOptionsStep, planWizardVMStep, + overview: { transformationPlans, editingPlanId }, form: { planWizardGeneralStep: { values: { vm_choice_radio } @@ -28,11 +30,24 @@ const mapStateToProps = ( ? [...planWizardVMStep.valid_vms, ...planWizardVMStep.invalid_vms, ...planWizardVMStep.conflict_vms] : [...planWizardVMStep.preselected_vms, ...planWizardVMStep.valid_vms]; + const editingPlan = findEditingPlan(transformationPlans, editingPlanId); + const configInfo = editingPlan && editingPlan.options && editingPlan.options.config_info; + return { ...planWizardAdvancedOptionsStep, ...ownProps.data, advancedOptionsStepForm, - vmStepSelectedVms: getVMStepSelectedVms(allVms, selectedVms) + vmStepSelectedVms: getVMStepSelectedVms(allVms, selectedVms), + initialValues: { + playbookVms: { + preMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'pre_service') : [], + postMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'post_service') : [] + }, + preMigrationPlaybook: editingPlan ? configInfo.pre_service_id : '', + postMigrationPlaybook: editingPlan ? configInfo.post_service_id : '' + }, + enableReinitialize: true, // Tells redux-form to use new initialValues when they change + editingPlan }; }; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardInstancePropertiesStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardInstancePropertiesStep/index.js index 4e611849e9..98a11f222b 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardInstancePropertiesStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardInstancePropertiesStep/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import PlanWizardInstancePropertiesStep from './PlanWizardInstancePropertiesStep'; import * as PlanWizardInstancePropertiesStepActions from './PlanWizardInstancePropertiesStepActions'; import reducer from './PlanWizardInstancePropertiesStepReducer'; -import { getVMStepSelectedVms } from '../PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStepSelectors'; +import { getVMStepSelectedVms } from '../PlanWizardAdvancedOptionsStep/helpers'; export const reducers = { planWizardInstancePropertiesStep: reducer }; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js index 7b1a323203..a396f402ae 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js @@ -105,8 +105,8 @@ export const _formatPreselectedVms = vmsQueryResults => vmsQueryResults.map(result => ({ id: result.id, name: result.name, - cluster: result.ems_cluster.name, - path: '', // TODO [mturley] how can we fetch the path? + cluster: result.ems_cluster ? result.ems_cluster.name : '', + path: '', // TODO [mturley] we need to fetch the path from a new API attribute on this query allocated_size: numeral(result.allocated_disk_storage).format('0.00b'), selected: true, valid: true, From ade8162cf167f4e9ab998e42520cbe3e0d1599bd Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 14:51:24 -0400 Subject: [PATCH 09/27] Add PUT action creator for saving edited plan --- .../PlanWizardAdvancedOptionsStep/helpers.js | 3 ++- .../PlanWizardGeneralStep.js | 1 + .../PlanWizardResultsStep.js | 16 ++++++++++++---- .../PlanWizardResultsStepActions.js | 18 ++++++++++++++++++ .../components/PlanWizardResultsStep/index.js | 9 +++++++-- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js index 1081b165f8..0ba24d9ce4 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js @@ -3,7 +3,8 @@ export const getVMStepSelectedVms = (allVms, selectedVms) => allVms.filter(vm => // Property can be 'pre_service' or 'post_service'. // Returns an array of vm ids which have a truthy value for that property in the plan being edited. export const getVmIdsWithProperty = (editingPlan, property) => { - const actions = editingPlan && editingPlan.options && editingPlan.options.config_info && editingPlan.options.config_info.actions; + const actions = + editingPlan && editingPlan.options && editingPlan.options.config_info && editingPlan.options.config_info.actions; if (!actions) return []; const actionsWithProperty = actions.filter(action => action[property]); return actionsWithProperty.map(action => action.vm_id); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js index 13e42b9073..45d500948f 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js @@ -8,6 +8,7 @@ import { BootstrapSelect } from '../../../../../common/forms/BootstrapSelect'; import { validation } from '../../../../../../../../common/constants'; import { asyncValidate, onChange } from './helpers'; +// TODO disable the mapping selection when editing const PlanWizardGeneralStep = ({ transformationMappings }) => (
(
@@ -71,7 +74,9 @@ class PlanWizardResultsStep extends React.Component { } PlanWizardResultsStep.propTypes = { postPlansUrl: PropTypes.string, + putPlansUrl: PropTypes.string, postMigrationPlansAction: PropTypes.func, + putMigrationPlansAction: PropTypes.func, plansBody: PropTypes.object, planSchedule: PropTypes.string, isPostingPlans: PropTypes.bool, @@ -79,11 +84,14 @@ PlanWizardResultsStep.propTypes = { errorPostingPlans: PropTypes.object, migrationPlansResult: PropTypes.object, migrationRequestsResult: PropTypes.object, - hidePlanWizardAction: PropTypes.func + hidePlanWizardAction: PropTypes.func, + editingPlan: PropTypes.object }; PlanWizardResultsStep.defaultProps = { postPlansUrl: '/api/service_templates', + putPlansUrl: '/api/service_templates', postMigrationPlansAction: noop, + putMigrationPlansAction: noop, plansBody: {}, planSchedule: '', isPostingPlans: true, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js index b4fcfefa4f..e9ab067f2f 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js @@ -35,3 +35,21 @@ const _postMigrationPlansActionCreator = (url, migrationPlans, planSchedule) => export const postMigrationPlansAction = (url, migrationPlans, planSchedule) => _postMigrationPlansActionCreator(url, migrationPlans, planSchedule); + +const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedule) => dispatch => + dispatch({ + type: POST_V2V_MIGRATION_PLANS, + payload: new Promise((resolve, reject) => { + API.put(`${url}/${planId}`, migrationPlans) + .then(response => { + resolve(response); + if (planSchedule === 'migration_plan_now') { + postMigrationRequestsAction(response, dispatch); + } + }) + .catch(e => reject(e)); + }) + }); + +export const putMigrationPlansAction = (url, migrationPlans, planSchedule) => + _putMigrationPlansActionCreator(url, migrationPlans, planSchedule); \ No newline at end of file diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/index.js index 9194c51fc9..9708057a49 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/index.js @@ -3,13 +3,18 @@ import PlanWizardResultsStep from './PlanWizardResultsStep'; import * as PlanWizardResultsStepActions from './PlanWizardResultsStepActions'; import reducer from './PlanWizardResultsStepReducer'; +import { findEditingPlan } from '../../PlanWizardSelectors'; export const reducers = { planWizardResultsStep: reducer }; -const mapStateToProps = ({ planWizardResultsStep, planWizard }, ownProps) => ({ +const mapStateToProps = ( + { planWizardResultsStep, planWizard, overview: { transformationPlans, editingPlanId } }, + ownProps +) => ({ ...planWizardResultsStep, ...planWizard, - ...ownProps.data + ...ownProps.data, + editingPlan: findEditingPlan(transformationPlans, editingPlanId) }); const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(stateProps, ownProps.data, dispatchProps); From b422fcff0ce21e081bb9c2035ceecec06eb39132 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 16:23:01 -0400 Subject: [PATCH 10/27] Fix rendering while saving plan edits --- .../Overview/screens/PlanWizard/PlanWizard.js | 14 ++--- .../PlanWizardResultsStep.js | 56 ++++++++++++------- .../PlanWizardResultsStepActions.js | 4 +- .../PlanWizardResultsStepConstants.js | 1 + .../PlanWizardResultsStepReducer.js | 21 ++++++- .../Overview/screens/PlanWizard/helpers.js | 4 +- 6 files changed, 65 insertions(+), 35 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index ac39f46534..1e8b0da5d0 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -111,7 +111,8 @@ class PlanWizard extends React.Component { hideConfirmModalAction, showAlertAction, hideAlertAction, - setMetadataWithNextButtonClickedAction + setMetadataWithNextButtonClickedAction, + editingPlan } = this.props; const wizardSteps = this.getWizardSteps(); @@ -149,7 +150,8 @@ class PlanWizard extends React.Component { onConfirm }); } else if (activeStep.id === stepIDs.scheduleStep) { - const plansBody = createMigrationPlans(planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep); + const isEditing = !!editingPlan; + const plansBody = createMigrationPlans(planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep, isEditing); setPlanScheduleAction(planWizardScheduleStep.values.migration_plan_choice_radio); setPlansBodyAction(plansBody); @@ -189,10 +191,6 @@ class PlanWizard extends React.Component { editingPlan } = this.props; - // TODO [mturley] remove me: - if (editingPlan) console.log('TODO: edit this plan: ', editingPlan); - if (!editingPlan) console.log('Not editing a plan'); - const wizardSteps = this.getWizardSteps(); const { activeStepIndex, plansBody } = this.state; @@ -212,6 +210,8 @@ class PlanWizard extends React.Component { !this.props.planWizardVMStep.values.selectedVms || this.props.planWizardVMStep.values.selectedVms.length === 0)); + const saveButtonLabel = editingPlan ? __('Save') : __('Create'); + return ( @@ -244,7 +244,7 @@ class PlanWizard extends React.Component { onClick={onFinalStep ? hidePlanWizardAction : this.nextStep} disabled={disableNextStep} > - {onFinalStep ? __('Close') : currentStepProp === stepIDs.scheduleStep ? __('Create') : __('Next')} + {onFinalStep ? __('Close') : currentStepProp === stepIDs.scheduleStep ? saveButtonLabel : __('Next')} diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js index a1d955bbed..6c5618e7a4 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js @@ -11,6 +11,27 @@ class PlanWizardResultsStep extends React.Component { putMigrationPlansAction(putPlansUrl, editingPlan.id, plansBody, planSchedule); } } + renderSpinner = (title, message) => ( +
+ +

{title}

+

+ {message} +

+
+ ); + renderError = (title, message, closeAction) => ( +
+
+ +
+

{title}

+

{message}

+ +
+ ); renderResult = (migrationPlanMessage, migrationPlanFollowupMessage, migrationPlanIcon) => (
@@ -27,39 +48,29 @@ class PlanWizardResultsStep extends React.Component { const { isPostingPlans, isRejectedPostingPlans, + isPuttingPlans, + isRejectedPuttingPlans, migrationPlansResult, migrationRequestsResult, errorPostingPlans, + errorPuttingPlans, plansBody, planSchedule, hidePlanWizardAction } = this.props; if (isPostingPlans) { - return ( -
- -

{__('Creating Migration Plans...')}

-

- {__('Please wait while infrastructure mapping is created.')} -

-
- ); + return this.renderSpinner(__('Creating Migration Plan...'), __('Please wait while the migration plan is created.')); } else if (isRejectedPostingPlans) { const errorData = errorPostingPlans && errorPostingPlans.data; const errorMessage = errorData && errorData.error && errorData.error.message; - return ( -
-
- -
-

{__('Error Creating Migration Plans')}

-

{errorMessage}

- -
- ); + return this.renderError(__('Error Creating Migration Plan'), errorMessage, hidePlanWizardAction); + } else if (isPuttingPlans) { + return this.renderSpinner(__('Saving Migration Plan...'), __('Please wait while the migration plan is saved.')); + } else if (isRejectedPuttingPlans) { + const errorData = errorPuttingPlans && errorPuttingPlans.data; + const errorMessage = errorData && errorData.error && errorData.error.message; + return this.renderError(__('Error Saving Migration Plan'), errorMessage, hidePlanWizardAction); } else if (planSchedule === 'migration_plan_later' && migrationPlansResult) { const migrationPlanSaved = sprintf(__(" Migration Plan: '%s' has been saved"), plansBody.name); const migrationPlanFollowupMessage = __('Select Migrate on the Overview page to begin migration'); @@ -82,6 +93,9 @@ PlanWizardResultsStep.propTypes = { isPostingPlans: PropTypes.bool, isRejectedPostingPlans: PropTypes.bool, errorPostingPlans: PropTypes.object, + isPuttingPlans: PropTypes.bool, + isRejectedPuttingPlans: PropTypes.bool, + errorPuttingPlans: PropTypes.object, migrationPlansResult: PropTypes.object, migrationRequestsResult: PropTypes.object, hidePlanWizardAction: PropTypes.func, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js index e9ab067f2f..3de454bd7c 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js @@ -1,5 +1,5 @@ import API from '../../../../../../../../common/API'; -import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS } from './PlanWizardResultsStepConstants'; +import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS, PUT_V2V_MIGRATION_PLANS } from './PlanWizardResultsStepConstants'; export { hidePlanWizardAction } from '../../PlanWizardActions'; @@ -38,7 +38,7 @@ export const postMigrationPlansAction = (url, migrationPlans, planSchedule) => const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedule) => dispatch => dispatch({ - type: POST_V2V_MIGRATION_PLANS, + type: PUT_V2V_MIGRATION_PLANS, payload: new Promise((resolve, reject) => { API.put(`${url}/${planId}`, migrationPlans) .then(response => { diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepConstants.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepConstants.js index 8b87082e21..fd01293218 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepConstants.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepConstants.js @@ -1,2 +1,3 @@ export const POST_V2V_MIGRATION_PLANS = 'POST_V2V_MIGRATION_PLANS'; +export const PUT_V2V_MIGRATION_PLANS = 'PUT_V2V_MIGRATION_PLANS'; export const POST_V2V_MIGRATION_REQUESTS = 'POST_V2V_MIGRATION_REQUESTS'; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js index 11621abd50..e9d14d94b4 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js @@ -1,12 +1,15 @@ import Immutable from 'seamless-immutable'; -import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS } from './PlanWizardResultsStepConstants'; +import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS, PUT_V2V_MIGRATION_PLANS } from './PlanWizardResultsStepConstants'; const initialState = Immutable({ - isPostingPlans: true, + isPostingPlans: false, isRejectedPostingPlans: false, errorPostingPlans: null, - migrationPlansResult: {} + migrationPlansResult: {}, + isPuttingPlans: false, + isRejectedPuttingPlans: false, + errorPuttingPlans: null }); export default (state = initialState, action) => { @@ -23,6 +26,18 @@ export default (state = initialState, action) => { .set('errorPostingPlans', action.payload) .set('isRejectedPostingPlans', true) .set('isPostingPlans', false); + case `${PUT_V2V_MIGRATION_PLANS}_PENDING`: + return state.set('isPuttingPlans', true); + case `${PUT_V2V_MIGRATION_PLANS}_FULFILLED`: + return state + .set('migrationPlansResult', action.payload) + .set('isRejectedPuttingPlans', false) + .set('isPuttingPlans', false); + case `${PUT_V2V_MIGRATION_PLANS}_REJECTED`: + return state + .set('errorPuttingPlans', action.payload) + .set('isRejectedPuttingPlans', true) + .set('isPuttingPlans', false); case `${POST_V2V_MIGRATION_REQUESTS}_PENDING`: return state.set('isPostingRequests', true); case `${POST_V2V_MIGRATION_REQUESTS}_FULFILLED`: diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js index 44900e1f34..ada1c230aa 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js @@ -1,4 +1,4 @@ -export const createMigrationPlans = (planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep) => { +export const createMigrationPlans = (planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep, isEditing = false) => { const planName = planWizardGeneralStep.values.name; const planDescription = planWizardGeneralStep.values.description; const infrastructureMapping = planWizardGeneralStep.values.infrastructure_mapping; @@ -19,7 +19,7 @@ export const createMigrationPlans = (planWizardGeneralStep, planWizardVMStep, pl return { name: planName, description: planDescription, - prov_type: 'generic_transformation_plan', + prov_type: isEditing ? 'transformation_plan' : 'generic_transformation_plan', config_info: { transformation_mapping_id: infrastructureMapping, pre_service_id: preMigrationPlaybook, From c21d55102e9d892f3e21f9f097e96c6a239f23a4 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 16:41:36 -0400 Subject: [PATCH 11/27] Catch duplicate VMs in discovery step, fix lint error --- .../Overview/screens/PlanWizard/PlanWizard.js | 7 ++++++- .../PlanWizardResultsStep.js | 21 +++++++++++++------ .../PlanWizardResultsStepActions.js | 8 +++++-- .../PlanWizardResultsStepReducer.js | 6 +++++- .../PlanWizardVMStep/PlanWizardVMStep.js | 9 ++++++-- .../Overview/screens/PlanWizard/helpers.js | 7 ++++++- 6 files changed, 45 insertions(+), 13 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index 1e8b0da5d0..3cc518c2c9 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -151,7 +151,12 @@ class PlanWizard extends React.Component { }); } else if (activeStep.id === stepIDs.scheduleStep) { const isEditing = !!editingPlan; - const plansBody = createMigrationPlans(planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep, isEditing); + const plansBody = createMigrationPlans( + planWizardGeneralStep, + planWizardVMStep, + planWizardAdvancedOptionsStep, + isEditing + ); setPlanScheduleAction(planWizardScheduleStep.values.migration_plan_choice_radio); setPlansBodyAction(plansBody); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js index 6c5618e7a4..fd2846f016 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js @@ -4,7 +4,15 @@ import { noop, Spinner } from 'patternfly-react'; class PlanWizardResultsStep extends React.Component { componentDidMount() { - const { postPlansUrl, postMigrationPlansAction, putPlansUrl, putMigrationPlansAction, plansBody, planSchedule, editingPlan } = this.props; + const { + postPlansUrl, + postMigrationPlansAction, + putPlansUrl, + putMigrationPlansAction, + plansBody, + planSchedule, + editingPlan + } = this.props; if (!editingPlan) { postMigrationPlansAction(postPlansUrl, plansBody, planSchedule); } else { @@ -15,9 +23,7 @@ class PlanWizardResultsStep extends React.Component {

{title}

-

- {message} -

+

{message}

); renderError = (title, message, closeAction) => ( @@ -27,7 +33,7 @@ class PlanWizardResultsStep extends React.Component {

{title}

{message}

-
@@ -60,7 +66,10 @@ class PlanWizardResultsStep extends React.Component { } = this.props; if (isPostingPlans) { - return this.renderSpinner(__('Creating Migration Plan...'), __('Please wait while the migration plan is created.')); + return this.renderSpinner( + __('Creating Migration Plan...'), + __('Please wait while the migration plan is created.') + ); } else if (isRejectedPostingPlans) { const errorData = errorPostingPlans && errorPostingPlans.data; const errorMessage = errorData && errorData.error && errorData.error.message; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js index 3de454bd7c..e9398f93d4 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js @@ -1,5 +1,9 @@ import API from '../../../../../../../../common/API'; -import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS, PUT_V2V_MIGRATION_PLANS } from './PlanWizardResultsStepConstants'; +import { + POST_V2V_MIGRATION_PLANS, + POST_V2V_MIGRATION_REQUESTS, + PUT_V2V_MIGRATION_PLANS +} from './PlanWizardResultsStepConstants'; export { hidePlanWizardAction } from '../../PlanWizardActions'; @@ -52,4 +56,4 @@ const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedu }); export const putMigrationPlansAction = (url, migrationPlans, planSchedule) => - _putMigrationPlansActionCreator(url, migrationPlans, planSchedule); \ No newline at end of file + _putMigrationPlansActionCreator(url, migrationPlans, planSchedule); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js index e9d14d94b4..6ffe34d7fb 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepReducer.js @@ -1,6 +1,10 @@ import Immutable from 'seamless-immutable'; -import { POST_V2V_MIGRATION_PLANS, POST_V2V_MIGRATION_REQUESTS, PUT_V2V_MIGRATION_PLANS } from './PlanWizardResultsStepConstants'; +import { + POST_V2V_MIGRATION_PLANS, + POST_V2V_MIGRATION_REQUESTS, + PUT_V2V_MIGRATION_PLANS +} from './PlanWizardResultsStepConstants'; const initialState = Immutable({ isPostingPlans: false, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 7c169dd094..3471488573 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -139,8 +139,13 @@ class PlanWizardVMStep extends React.Component { const validVmsWithSelections = discoveryMode ? validVms : validVms.filter(vm => vm.valid === true).map(vm => ({ ...vm, selected: true })); - const combined = [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsWithSelections]; - + // In case the discovery service returns some of the VMs we pre-selected: + const validVmsDeduped = !editingPlan + ? validVmsWithSelections + : validVmsWithSelections.filter( + validVm => !preselected_vms.some(preselectedVm => validVm.id === preselectedVm.id) + ); + const combined = [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsDeduped]; if (combined.length) { return ( diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js index ada1c230aa..876cd9784f 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/helpers.js @@ -1,4 +1,9 @@ -export const createMigrationPlans = (planWizardGeneralStep, planWizardVMStep, planWizardAdvancedOptionsStep, isEditing = false) => { +export const createMigrationPlans = ( + planWizardGeneralStep, + planWizardVMStep, + planWizardAdvancedOptionsStep, + isEditing = false +) => { const planName = planWizardGeneralStep.values.name; const planDescription = planWizardGeneralStep.values.description; const infrastructureMapping = planWizardGeneralStep.values.infrastructure_mapping; From 3978a9ea6ee56bb4d3b45269e2878edeb9a0f6f2 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 10 Sep 2018 17:14:23 -0400 Subject: [PATCH 12/27] Disable Mapping selection when editing a plan --- .../PlanWizardGeneralStep/PlanWizardGeneralStep.js | 6 ++++-- .../react/screens/App/common/forms/BootstrapSelect.js | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js index 45d500948f..120b7414e6 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js @@ -9,7 +9,7 @@ import { validation } from '../../../../../../../../common/constants'; import { asyncValidate, onChange } from './helpers'; // TODO disable the mapping selection when editing -const PlanWizardGeneralStep = ({ transformationMappings }) => ( +const PlanWizardGeneralStep = ({ transformationMappings, editingPlan }) => ( ( inline_label labelWidth={2} controlWidth={9} + disabled={!!editingPlan} /> ( ); PlanWizardGeneralStep.propTypes = { - transformationMappings: PropTypes.array + transformationMappings: PropTypes.array, + editingPlan: PropTypes.object }; export default reduxForm({ diff --git a/app/javascript/react/screens/App/common/forms/BootstrapSelect.js b/app/javascript/react/screens/App/common/forms/BootstrapSelect.js index 4e45d335eb..ff7e94cc0c 100644 --- a/app/javascript/react/screens/App/common/forms/BootstrapSelect.js +++ b/app/javascript/react/screens/App/common/forms/BootstrapSelect.js @@ -38,7 +38,8 @@ export class BootstrapSelect extends React.Component { option_key, option_value, choose_text, - meta: { visited, error, active } + meta: { visited, error, active }, + disabled } = this.props; const { inline_label, stacked_label, labelWidth, controlWidth, allowClear, ...otherProps } = this.props; @@ -62,6 +63,7 @@ export class BootstrapSelect extends React.Component { data-live-search={data_live_search} className={`form-control ${input.name}_select`} {...input} + disabled={!!disabled} >
From 65fc1f6e52d0bb331831913588ce1f07b7617245 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 11 Sep 2018 13:08:56 -0400 Subject: [PATCH 14/27] Warn before proceeding to CSV import when editing a plan --- .../Overview/screens/PlanWizard/PlanWizard.js | 14 +++++++++++++- .../screens/PlanWizard/PlanWizardConstants.js | 17 +++++++++++++++++ .../PlanWizardVMStep/PlanWizardVMStep.js | 13 ++----------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index 3cc518c2c9..8b30e945a5 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -9,7 +9,7 @@ import componentRegistry from '../../../../../../components/componentRegistry'; import PlanWizardGeneralStep from '../PlanWizard/components/PlanWizardGeneralStep'; import PlanWizardScheduleStep from '../PlanWizard/components/PlanWizardScheduleStep'; -import { stepIDs } from './PlanWizardConstants'; +import { stepIDs, overwriteCsvConfirmModalProps } from './PlanWizardConstants'; class PlanWizard extends React.Component { planWizardVMStepContainer = componentRegistry.markup('PlanWizardVMStepContainer'); @@ -126,6 +126,18 @@ class PlanWizard extends React.Component { return; } hideAlertAction(); + + if (editingPlan && planWizardGeneralStep.values.vm_choice_radio === 'vms_via_csv') { + // If the user selected CSV and they are editing a plan, warn before proceeding. + showConfirmModalAction({ + ...overwriteCsvConfirmModalProps, + onConfirm: () => { + hideConfirmModalAction(); + this.goToStepId(stepIDs.vmStep); + } + }); + return; // Don't proceed until the user confirms the warning. + } } if ( diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js index e4547ad2f7..22a565dcee 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js @@ -1,3 +1,6 @@ +import React from 'react'; +import { Icon } from 'patternfly-react'; + export const V2V_SET_PLANS_BODY = 'V2V_SET_PLANS_BODY'; export const V2V_SET_PLAN_SCHEDULE = 'V2V_SET_PLAN_SCHEDULE'; export const V2V_PLAN_WIZARD_SHOW_ALERT = 'V2V_PLAN_WIZARD_SHOW_ALERT'; @@ -11,3 +14,17 @@ export const stepIDs = { scheduleStep: 'planWizardScheduleStep', resultsStep: 'planWizardResultsStep' }; + +export const overwriteCsvConfirmModalProps = { + title: __('Overwrite Import File'), + body: ( + +

{__('Importing a new VM list file will overwrite the contents of the existing list.')}

+

{__('Are you sure you want to import a new file?')}

+
+ ), + icon: , + confirmButtonLabel: __('Import'), + dialogClassName: 'plan-wizard-confirm-modal', + backdropClassName: 'plan-wizard-confirm-backdrop' +}; \ No newline at end of file diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 3471488573..32b4df3728 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -7,6 +7,7 @@ import { Button, Icon } from 'patternfly-react'; import PlanWizardVMStepTable from './components/PlanWizardVMStepTable'; import CSVDropzoneField from './components/CSVDropzoneField'; import { getVmIds } from './helpers'; +import { overwriteCsvConfirmModalProps } from '../../PlanWizardConstants'; class PlanWizardVMStep extends React.Component { componentDidMount() { @@ -42,17 +43,7 @@ class PlanWizardVMStep extends React.Component { showOverwriteCsvConfirmModal = () => { const { csvImportAction, showConfirmModalAction, hideConfirmModalAction } = this.props; showConfirmModalAction({ - title: __('Overwrite Import File'), - body: ( - -

{__('Importing a new VM list file will overwrite the contents of the existing list.')}

-

{__('Are you sure you want to import a new file?')}

-
- ), - icon: , - confirmButtonLabel: __('Import'), - dialogClassName: 'plan-wizard-confirm-modal', - backdropClassName: 'plan-wizard-confirm-backdrop', + ...overwriteCsvConfirmModalProps, onConfirm: () => { hideConfirmModalAction(); csvImportAction(); From bef5e4c3ef6be7378f6e2ac82adb1075d630244c Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 11 Sep 2018 13:21:22 -0400 Subject: [PATCH 15/27] Don't use pre-selected VMs in CSV mode --- .../components/PlanWizardVMStep/PlanWizardVMStep.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 32b4df3728..5a90ca872b 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -136,7 +136,9 @@ class PlanWizardVMStep extends React.Component { : validVmsWithSelections.filter( validVm => !preselected_vms.some(preselectedVm => validVm.id === preselectedVm.id) ); - const combined = [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsDeduped]; + const combined = discoveryMode + ? [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsDeduped] + : [...inValidsVms, ...conflictVms, ...validVmsWithSelections]; if (combined.length) { return ( From b7353015a2e2796fbd7186962a33599403025437 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Tue, 11 Sep 2018 15:41:00 -0400 Subject: [PATCH 16/27] Lint fixes --- .../App/Overview/screens/PlanWizard/PlanWizardConstants.js | 2 +- .../PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js index 22a565dcee..c9c5fe0f11 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js @@ -27,4 +27,4 @@ export const overwriteCsvConfirmModalProps = { confirmButtonLabel: __('Import'), dialogClassName: 'plan-wizard-confirm-modal', backdropClassName: 'plan-wizard-confirm-backdrop' -}; \ No newline at end of file +}; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js index 5a90ca872b..812cee5c3a 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStep.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import Immutable from 'seamless-immutable'; import { Field, reduxForm } from 'redux-form'; import { length } from 'redux-form-validators'; -import { Button, Icon } from 'patternfly-react'; +import { Button } from 'patternfly-react'; import PlanWizardVMStepTable from './components/PlanWizardVMStepTable'; import CSVDropzoneField from './components/CSVDropzoneField'; import { getVmIds } from './helpers'; From 3c2ff277a83f9cc053d9bb45a4794dd1c91ada96 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Wed, 12 Sep 2018 15:57:09 -0400 Subject: [PATCH 17/27] Add v_parent_blue_folder_path to pre-filled VMs in VM step --- .../PlanWizardVMStep/PlanWizardVMStepActions.js | 15 +++++++-------- .../components/PlanWizardVMStep/helpers.js | 4 +++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js index bc76a7e233..721f56fee3 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/PlanWizardVMStepActions.js @@ -51,15 +51,14 @@ const _queryPreselectedVmsActionCreator = ids => dispatch => { return dispatch({ type: QUERY_V2V_PLAN_VMS, - payload: API.post('/api/vms?expand=resources&attributes=name,ems_cluster.name,allocated_disk_storage', { - action: 'query', - resources - }) + payload: API.post( + '/api/vms?expand=resources&attributes=name,ems_cluster.name,allocated_disk_storage,ext_management_system.name,v_parent_blue_folder_display_path', + { + action: 'query', + resources + } + ) }); }; export const queryPreselectedVmsAction = ids => _queryPreselectedVmsActionCreator(ids); - -// TODO [mturley] format allocated_disk_storage with numeral.js, -// TODO [mturley] figure out details of getting the path of a VM -// TODO [mturley] talk to Aparna about this diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js index a396f402ae..3bb1bafbb0 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardVMStep/helpers.js @@ -106,7 +106,9 @@ export const _formatPreselectedVms = vmsQueryResults => id: result.id, name: result.name, cluster: result.ems_cluster ? result.ems_cluster.name : '', - path: '', // TODO [mturley] we need to fetch the path from a new API attribute on this query + path: result.ext_management_system + ? `${result.ext_management_system.name}/${result.v_parent_blue_folder_display_path}` + : '', allocated_size: numeral(result.allocated_disk_storage).format('0.00b'), selected: true, valid: true, From 5921126e13fe73970893d72265714384054601f6 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 13 Sep 2018 10:01:04 -0400 Subject: [PATCH 18/27] Fix wizard clearing on navigating backwards, remove unnecessary comments --- .../screens/App/Overview/screens/PlanWizard/PlanWizard.js | 3 +-- .../PlanWizardAdvancedOptionsStep.js | 1 - .../components/PlanWizardAdvancedOptionsStep/index.js | 1 + .../PlanWizard/components/PlanWizardGeneralStep/index.js | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index 8b30e945a5..3d0c10ad33 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -128,7 +128,6 @@ class PlanWizard extends React.Component { hideAlertAction(); if (editingPlan && planWizardGeneralStep.values.vm_choice_radio === 'vms_via_csv') { - // If the user selected CSV and they are editing a plan, warn before proceeding. showConfirmModalAction({ ...overwriteCsvConfirmModalProps, onConfirm: () => { @@ -136,7 +135,7 @@ class PlanWizard extends React.Component { this.goToStepId(stepIDs.vmStep); } }); - return; // Don't proceed until the user confirms the warning. + return; } } diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js index f3395fcfa2..9c3be33430 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js @@ -106,5 +106,4 @@ PlanWizardAdvancedOptionsStep.defaultProps = { export default reduxForm({ form: 'planWizardAdvancedOptionsStep', destroyOnUnmount: false - // initialValues prop is passed in via ./index })(PlanWizardAdvancedOptionsStep); diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js index 334c3c9549..c4e7cc71d1 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js @@ -47,6 +47,7 @@ const mapStateToProps = ( postMigrationPlaybook: editingPlan ? configInfo.post_service_id : '' }, enableReinitialize: true, // Tells redux-form to use new initialValues when they change + keepDirtyOnReinitialize: true, editingPlan }; }; diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js index bdf6738215..fcad629a54 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/index.js @@ -21,6 +21,7 @@ const mapStateToProps = ({ overview }) => { description: editingPlan ? editingPlan.description : '' }, enableReinitialize: true, // Tells redux-form to use new initialValues when they change + keepDirtyOnReinitialize: true, editingPlan }; }; From 640043bb88cdd9b238b3637bffca550510a44c62 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 13 Sep 2018 10:41:13 -0400 Subject: [PATCH 19/27] Remove old TODO comment --- .../components/PlanWizardGeneralStep/PlanWizardGeneralStep.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js index 120b7414e6..450497a516 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js @@ -8,7 +8,6 @@ import { BootstrapSelect } from '../../../../../common/forms/BootstrapSelect'; import { validation } from '../../../../../../../../common/constants'; import { asyncValidate, onChange } from './helpers'; -// TODO disable the mapping selection when editing const PlanWizardGeneralStep = ({ transformationMappings, editingPlan }) => ( Date: Thu, 13 Sep 2018 11:56:44 -0400 Subject: [PATCH 20/27] Fix duplicate VMs appearing on Advanced Options step --- .../components/PlanWizardAdvancedOptionsStep/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js index c4e7cc71d1..f1d95bcfbe 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js @@ -25,12 +25,17 @@ const mapStateToProps = ( }, ownProps ) => { + const editingPlan = findEditingPlan(transformationPlans, editingPlanId); + const validVmsDeduped = !editingPlan + ? planWizardVMStep.valid_vms + : planWizardVMStep.valid_vms.filter( + validVm => !planWizardVMStep.preselected_vms.some(preselectedVm => preselectedVm.id === validVm.id) + ); const allVms = vm_choice_radio === 'vms_via_csv' ? [...planWizardVMStep.valid_vms, ...planWizardVMStep.invalid_vms, ...planWizardVMStep.conflict_vms] - : [...planWizardVMStep.preselected_vms, ...planWizardVMStep.valid_vms]; + : [...planWizardVMStep.preselected_vms, ...validVmsDeduped]; - const editingPlan = findEditingPlan(transformationPlans, editingPlanId); const configInfo = editingPlan && editingPlan.options && editingPlan.options.config_info; return { From 1cfb858fbdf59c236ca14611c032fde92a370408 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 13 Sep 2018 14:21:38 -0400 Subject: [PATCH 21/27] Don't allow editing a plan with no associated mapping --- .../Overview/components/Migrations/MigrationsNotStartedList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js index 6a4a5800d2..b07f1d71b1 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js @@ -130,6 +130,7 @@ class MigrationsNotStartedList extends React.Component { e.stopPropagation(); showPlanWizardEditModeAction(plan.id); }} + disabled={isMissingMapping || loading === plan.href} > {__('Edit')} From e8d937cc7b0039f67b80a2872c99267e4734e4c4 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Thu, 13 Sep 2018 14:48:32 -0400 Subject: [PATCH 22/27] Fix Advanced Options Step Table selected count getting confused during edit --- .../components/PlanWizardAdvancedOptionsStep/helpers.js | 9 +++++---- .../components/PlanWizardAdvancedOptionsStep/index.js | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js index 0ba24d9ce4..0d4834d346 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js @@ -2,17 +2,18 @@ export const getVMStepSelectedVms = (allVms, selectedVms) => allVms.filter(vm => // Property can be 'pre_service' or 'post_service'. // Returns an array of vm ids which have a truthy value for that property in the plan being edited. -export const getVmIdsWithProperty = (editingPlan, property) => { +export const getVmIdsWithProperty = (editingPlan, property, vmStepSelectedVms) => { const actions = editingPlan && editingPlan.options && editingPlan.options.config_info && editingPlan.options.config_info.actions; if (!actions) return []; const actionsWithProperty = actions.filter(action => action[property]); - return actionsWithProperty.map(action => action.vm_id); + const vmIds = actionsWithProperty.map(action => action.vm_id); + return vmIds.filter(id => vmStepSelectedVms.some(vm => vm.id === id)); }; export const preselectPlaybooksForVms = (editingPlan, vms) => { - const vmIdsWithPreService = getVmIdsWithProperty(editingPlan, 'pre_service'); - const vmIdsWithPostService = getVmIdsWithProperty(editingPlan, 'post_service'); + const vmIdsWithPreService = getVmIdsWithProperty(editingPlan, 'pre_service', vms); + const vmIdsWithPostService = getVmIdsWithProperty(editingPlan, 'post_service', vms); return vms.map(vm => ({ ...vm, preMigration: vmIdsWithPreService.some(id => id === vm.id), diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js index f1d95bcfbe..1378847b0a 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/index.js @@ -37,16 +37,17 @@ const mapStateToProps = ( : [...planWizardVMStep.preselected_vms, ...validVmsDeduped]; const configInfo = editingPlan && editingPlan.options && editingPlan.options.config_info; + const vmStepSelectedVms = getVMStepSelectedVms(allVms, selectedVms); return { ...planWizardAdvancedOptionsStep, ...ownProps.data, advancedOptionsStepForm, - vmStepSelectedVms: getVMStepSelectedVms(allVms, selectedVms), + vmStepSelectedVms, initialValues: { playbookVms: { - preMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'pre_service') : [], - postMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'post_service') : [] + preMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'pre_service', vmStepSelectedVms) : [], + postMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'post_service', vmStepSelectedVms) : [] }, preMigrationPlaybook: editingPlan ? configInfo.pre_service_id : '', postMigrationPlaybook: editingPlan ? configInfo.post_service_id : '' From d895f0f5e2dbaa4c36aee1f64f96770e4f767b1e Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 14 Sep 2018 09:00:09 -0400 Subject: [PATCH 23/27] Fix putMigrationPlansAction function signature --- .../PlanWizardResultsStep/PlanWizardResultsStepActions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js index e9398f93d4..64a5b20877 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js @@ -55,5 +55,5 @@ const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedu }) }); -export const putMigrationPlansAction = (url, migrationPlans, planSchedule) => - _putMigrationPlansActionCreator(url, migrationPlans, planSchedule); +export const putMigrationPlansAction = (url, planId, migrationPlans, planSchedule) => + _putMigrationPlansActionCreator(url, planId, migrationPlans, planSchedule); From 6d513bc6088cf57fedbad9eff66a74316fe32ae3 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 14 Sep 2018 09:26:48 -0400 Subject: [PATCH 24/27] Actually disable edit menu item on orphaned plans --- .../components/Migrations/MigrationsNotStartedList.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js index b07f1d71b1..bb02fac8ad 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js @@ -93,6 +93,8 @@ class MigrationsNotStartedList extends React.Component { const migrationScheduled = plan.schedules && plan.schedules[0].run_at.start_time; const isMissingMapping = !plan.infraMappingName; + const editPlanDisabled = isMissingMapping || loading === plan.href; + return ( { e.stopPropagation(); - showPlanWizardEditModeAction(plan.id); + if (!editPlanDisabled) showPlanWizardEditModeAction(plan.id); }} - disabled={isMissingMapping || loading === plan.href} + disabled={editPlanDisabled} > {__('Edit')} From 7fb7fbd60c5a511514a9a5d211f8fce8c7768401 Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Fri, 14 Sep 2018 11:16:09 -0400 Subject: [PATCH 25/27] POST to edit a plan instead of PUT --- .../PlanWizardResultsStep.js | 14 +++++++------- .../PlanWizardResultsStepActions.js | 15 ++++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js index fd2846f016..f1ccb3f0e5 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js @@ -7,8 +7,8 @@ class PlanWizardResultsStep extends React.Component { const { postPlansUrl, postMigrationPlansAction, - putPlansUrl, - putMigrationPlansAction, + editPlansUrl, + editMigrationPlansAction, plansBody, planSchedule, editingPlan @@ -16,7 +16,7 @@ class PlanWizardResultsStep extends React.Component { if (!editingPlan) { postMigrationPlansAction(postPlansUrl, plansBody, planSchedule); } else { - putMigrationPlansAction(putPlansUrl, editingPlan.id, plansBody, planSchedule); + editMigrationPlansAction(editPlansUrl, editingPlan.id, plansBody, planSchedule); } } renderSpinner = (title, message) => ( @@ -94,9 +94,9 @@ class PlanWizardResultsStep extends React.Component { } PlanWizardResultsStep.propTypes = { postPlansUrl: PropTypes.string, - putPlansUrl: PropTypes.string, + editPlansUrl: PropTypes.string, postMigrationPlansAction: PropTypes.func, - putMigrationPlansAction: PropTypes.func, + editMigrationPlansAction: PropTypes.func, plansBody: PropTypes.object, planSchedule: PropTypes.string, isPostingPlans: PropTypes.bool, @@ -112,9 +112,9 @@ PlanWizardResultsStep.propTypes = { }; PlanWizardResultsStep.defaultProps = { postPlansUrl: '/api/service_templates', - putPlansUrl: '/api/service_templates', + editPlansUrl: '/api/service_templates', postMigrationPlansAction: noop, - putMigrationPlansAction: noop, + editMigrationPlansAction: noop, plansBody: {}, planSchedule: '', isPostingPlans: true, diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js index 64a5b20877..78d8df3e65 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStepActions.js @@ -40,11 +40,15 @@ const _postMigrationPlansActionCreator = (url, migrationPlans, planSchedule) => export const postMigrationPlansAction = (url, migrationPlans, planSchedule) => _postMigrationPlansActionCreator(url, migrationPlans, planSchedule); -const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedule) => dispatch => - dispatch({ +const _editMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedule) => dispatch => { + const body = { + action: 'edit', + resource: { ...migrationPlans } + }; + return dispatch({ type: PUT_V2V_MIGRATION_PLANS, payload: new Promise((resolve, reject) => { - API.put(`${url}/${planId}`, migrationPlans) + API.post(`${url}/${planId}`, body) .then(response => { resolve(response); if (planSchedule === 'migration_plan_now') { @@ -54,6 +58,7 @@ const _putMigrationPlansActionCreator = (url, planId, migrationPlans, planSchedu .catch(e => reject(e)); }) }); +}; -export const putMigrationPlansAction = (url, planId, migrationPlans, planSchedule) => - _putMigrationPlansActionCreator(url, planId, migrationPlans, planSchedule); +export const editMigrationPlansAction = (url, planId, migrationPlans, planSchedule) => + _editMigrationPlansActionCreator(url, planId, migrationPlans, planSchedule); From 6dabc5727aa42769f77d7d92be4f794003fb2e6f Mon Sep 17 00:00:00 2001 From: Mike Turley Date: Mon, 24 Sep 2018 10:41:03 -0400 Subject: [PATCH 26/27] Change title of wizard to Edit Migration Plan or Create Migration Plan depending on state --- .../screens/App/Overview/screens/PlanWizard/PlanWizard.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js index 3d0c10ad33..d023b3682d 100644 --- a/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizard.js @@ -228,9 +228,11 @@ class PlanWizard extends React.Component { const saveButtonLabel = editingPlan ? __('Save') : __('Create'); + const wizardTitle = editingPlan ? __('Edit Migration Plan') : __('Create Migration Plan'); + return ( - + Date: Mon, 24 Sep 2018 15:47:08 -0400 Subject: [PATCH 27/27] Disable Edit menu item until backend support is ready --- .../Overview/components/Migrations/MigrationsNotStartedList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js index bb02fac8ad..effb981d88 100644 --- a/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js +++ b/app/javascript/react/screens/App/Overview/components/Migrations/MigrationsNotStartedList.js @@ -133,6 +133,7 @@ class MigrationsNotStartedList extends React.Component { if (!editPlanDisabled) showPlanWizardEditModeAction(plan.id); }} disabled={editPlanDisabled} + style={{ display: 'none' }} // Edit Plan is disabled until https://github.com/ManageIQ/manageiq/pull/17989 is ready > {__('Edit')}