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/__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/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 85dbec267e..effb981d88 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'; @@ -61,7 +61,8 @@ class MigrationsNotStartedList extends React.Component { fetchTransformationPlansUrl, plansMutatedWithMappingInfo, deleteTransformationPlanAction, - deleteTransformationPlanUrl + deleteTransformationPlanUrl, + showPlanWizardEditModeAction } = this.props; const sortedMigrations = this.sortedMigrations(); @@ -92,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(); + 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')} + { + hideConfirmModalAction(); + this.goToStepId(stepIDs.vmStep); + } + }); + return; + } } if ( @@ -149,7 +161,13 @@ 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); @@ -185,7 +203,8 @@ class PlanWizard extends React.Component { planWizardExitedAction, alertText, alertType, - hideAlertAction + hideAlertAction, + editingPlan } = this.props; const wizardSteps = this.getWizardSteps(); @@ -207,9 +226,13 @@ class PlanWizard extends React.Component { !this.props.planWizardVMStep.values.selectedVms || this.props.planWizardVMStep.values.selectedVms.length === 0)); + const saveButtonLabel = editingPlan ? __('Save') : __('Create'); + + const wizardTitle = editingPlan ? __('Edit Migration Plan') : __('Create Migration Plan'); + return ( - + - {onFinalStep ? __('Close') : currentStepProp === stepIDs.scheduleStep ? __('Create') : __('Next')} + {onFinalStep ? __('Close') : currentStepProp === stepIDs.scheduleStep ? saveButtonLabel : __('Next')} @@ -268,7 +291,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/PlanWizardConstants.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/PlanWizardConstants.js index e4547ad2f7..c9c5fe0f11 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' +}; 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..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,6 +1,10 @@ +export const findEditingPlan = (transformationPlans, editingPlanId) => + editingPlanId && transformationPlans.find(plan => plan.id === editingPlanId); + export const planWizardOverviewFilter = overview => ({ hidePlanWizard: overview.hidePlanWizard, - transformationMappings: overview.transformationMappings + transformationMappings: overview.transformationMappings, + 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/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/PlanWizardAdvancedOptionsStep.js index e7f7f803b7..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 @@ -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,5 @@ PlanWizardAdvancedOptionsStep.defaultProps = { export default reduxForm({ form: 'planWizardAdvancedOptionsStep', - initialValues: { - playbookVms: { - preMigration: [], - postMigration: [] - }, - preMigrationPlaybook: '', - postMigrationPlaybook: '' - }, destroyOnUnmount: false })(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..0d4834d346 --- /dev/null +++ b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardAdvancedOptionsStep/helpers.js @@ -0,0 +1,22 @@ +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, 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]); + 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', vms); + const vmIdsWithPostService = getVmIdsWithProperty(editingPlan, 'post_service', vms); + 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 5e2b850294..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 @@ -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 } @@ -23,16 +25,36 @@ 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.valid_vms; + : [...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', vmStepSelectedVms) : [], + postMigration: editingPlan ? getVmIdsWithProperty(editingPlan, 'post_service', vmStepSelectedVms) : [] + }, + preMigrationPlaybook: editingPlan ? configInfo.pre_service_id : '', + 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/PlanWizardGeneralStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardGeneralStep/PlanWizardGeneralStep.js index 13e42b9073..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,7 @@ import { BootstrapSelect } from '../../../../../common/forms/BootstrapSelect'; import { validation } from '../../../../../../../../common/constants'; import { asyncValidate, onChange } from './helpers'; -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/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..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 @@ -1,16 +1,30 @@ 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 + keepDirtyOnReinitialize: true, + editingPlan + }; +}; const actions = { showAlertAction, hideAlertAction }; 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/PlanWizardResultsStep/PlanWizardResultsStep.js b/app/javascript/react/screens/App/Overview/screens/PlanWizard/components/PlanWizardResultsStep/PlanWizardResultsStep.js index c2e8a6bd3c..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 @@ -4,10 +4,40 @@ import { noop, Spinner } from 'patternfly-react'; class PlanWizardResultsStep extends React.Component { componentDidMount() { - const { postPlansUrl, postMigrationPlansAction, plansBody, planSchedule } = this.props; - - postMigrationPlansAction(postPlansUrl, plansBody, planSchedule); + const { + postPlansUrl, + postMigrationPlansAction, + editPlansUrl, + editMigrationPlansAction, + plansBody, + planSchedule, + editingPlan + } = this.props; + if (!editingPlan) { + postMigrationPlansAction(postPlansUrl, plansBody, planSchedule); + } else { + editMigrationPlansAction(editPlansUrl, editingPlan.id, plansBody, planSchedule); + } } + renderSpinner = (title, message) => ( +
+ +

{title}

+

{message}

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

{title}

+

{message}

+ +
+ ); renderResult = (migrationPlanMessage, migrationPlanFollowupMessage, migrationPlanIcon) => (
@@ -24,39 +54,32 @@ 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'); @@ -71,19 +94,27 @@ class PlanWizardResultsStep extends React.Component { } PlanWizardResultsStep.propTypes = { postPlansUrl: PropTypes.string, + editPlansUrl: PropTypes.string, postMigrationPlansAction: PropTypes.func, + editMigrationPlansAction: PropTypes.func, plansBody: PropTypes.object, planSchedule: PropTypes.string, 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 + hidePlanWizardAction: PropTypes.func, + editingPlan: PropTypes.object }; PlanWizardResultsStep.defaultProps = { postPlansUrl: '/api/service_templates', + editPlansUrl: '/api/service_templates', postMigrationPlansAction: 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 b4fcfefa4f..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 @@ -1,5 +1,9 @@ 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'; @@ -35,3 +39,26 @@ const _postMigrationPlansActionCreator = (url, migrationPlans, planSchedule) => export const postMigrationPlansAction = (url, migrationPlans, planSchedule) => _postMigrationPlansActionCreator(url, migrationPlans, planSchedule); + +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.post(`${url}/${planId}`, body) + .then(response => { + resolve(response); + if (planSchedule === 'migration_plan_now') { + postMigrationRequestsAction(response, dispatch); + } + }) + .catch(e => reject(e)); + }) + }); +}; + +export const editMigrationPlansAction = (url, planId, migrationPlans, planSchedule) => + _editMigrationPlansActionCreator(url, planId, migrationPlans, planSchedule); 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..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,12 +1,19 @@ 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 +30,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/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); 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..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,16 +3,22 @@ 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'; +import { overwriteCsvConfirmModalProps } from '../../PlanWizardConstants'; class PlanWizardVMStep extends React.Component { componentDidMount() { - const { vm_choice_radio } = 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); + queryPreselectedVmsAction(vmIds); + } } componentDidUpdate(prevProps) { const { vm_choice_radio } = this.props; @@ -37,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(); @@ -64,8 +60,11 @@ class PlanWizardVMStep extends React.Component { valid_vms, invalid_vms, conflict_vms, + preselected_vms, validationServiceCalled, - csvImportAction + csvImportAction, + editingPlan, + isQueryingVms } = this.props; const discoveryMode = vm_choice_radio === 'vms_via_discovery'; @@ -121,7 +120,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( @@ -131,8 +130,15 @@ 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]; - + // 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 = discoveryMode + ? [...preselected_vms, ...inValidsVms, ...conflictVms, ...validVmsDeduped] + : [...inValidsVms, ...conflictVms, ...validVmsWithSelections]; if (combined.length) { return ( @@ -140,7 +146,9 @@ class PlanWizardVMStep extends React.Component { name="selectedVms" component={PlanWizardVMStepTable} rows={combined} - initialSelectedRows={discoveryMode ? [] : validVmsWithSelections.map(r => r.id)} + initialSelectedRows={ + discoveryMode ? preselected_vms.map(r => r.id) : validVmsWithSelections.map(r => r.id) + } onCsvImportAction={this.showOverwriteCsvConfirmModal} discoveryMode={discoveryMode} validate={[ @@ -187,7 +195,11 @@ 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, + preselected_vms: PropTypes.array, + editingPlan: PropTypes.object, + queryPreselectedVmsAction: PropTypes.func, + isQueryingVms: PropTypes.bool }; PlanWizardVMStep.defaultProps = { @@ -201,7 +213,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 d81b4b45bf..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 @@ -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,22 @@ export const csvParseErrorAction = errMsg => dispatch => { payload: errMsg }); }; + +const _queryPreselectedVmsActionCreator = 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,ext_management_system.name,v_parent_blue_folder_display_path', + { + action: 'query', + resources + } + ) + }); +}; + +export const queryPreselectedVmsAction = ids => _queryPreselectedVmsActionCreator(ids); 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..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 } from './PlanWizardVMStepConstants'; -import { _formatConflictVms, _formatInvalidVms, _formatValidVms } from './helpers'; +import { V2V_VALIDATE_VMS, V2V_VM_STEP_RESET, QUERY_V2V_PLAN_VMS } from './PlanWizardVMStepConstants'; +import { _formatConflictVms, _formatInvalidVms, _formatValidVms, _formatPreselectedVms } from './helpers'; const initialState = Immutable({ isValidatingVms: false, @@ -10,7 +10,11 @@ const initialState = Immutable({ errorValidatingVms: null, valid_vms: [], invalid_vms: [], - conflict_vms: [] + conflict_vms: [], + preselected_vms: [], + isQueryingVms: false, + isVmsQueryRejected: false, + vmsQueryError: null }); export default (state = initialState, action) => { @@ -47,12 +51,40 @@ 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('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('preselected_vms', []); + } + case `${QUERY_V2V_PLAN_VMS}_REJECTED`: + return state + .set('isQueryingVms', false) + .set('isVmsQueryRejected', true) + .set('vmsQueryError', action.payload) + .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 985812a46d..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 @@ -93,3 +93,24 @@ 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); + +export const _formatPreselectedVms = vmsQueryResults => + vmsQueryResults.map(result => ({ + id: result.id, + name: result.name, + cluster: result.ems_cluster ? result.ems_cluster.name : '', + 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, + reason: V2V_VM_POST_VALIDATION_REASONS.ok + })); 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( 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..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) => { +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 +24,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, 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} >