Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
Merge pull request #474 from priley86/duplicate-plans
Browse files Browse the repository at this point in the history
[#451] duplicate plan name validation
(cherry picked from commit 8bf1179)

https://bugzilla.redhat.com/show_bug.cgi?id=1608768
  • Loading branch information
AparnaKarve authored and simaishi committed Jul 26, 2018
1 parent 17e475e commit 594bd65
Show file tree
Hide file tree
Showing 15 changed files with 131 additions and 16 deletions.
6 changes: 6 additions & 0 deletions app/javascript/react/screens/App/Overview/Overview.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Overview extends React.Component {
fetchTransformationMappingsAction,
fetchTransformationPlansUrl,
fetchTransformationPlansAction,
fetchArchivedTransformationPlansUrl,
fetchNetworksUrl,
fetchNetworksAction,
fetchDatastoresUrl,
Expand All @@ -54,6 +55,11 @@ class Overview extends React.Component {
this.startPolling();
}
});
// fetch archived plans initially so we have them for plan name validation in plan wizard
fetchTransformationPlansAction({
url: fetchArchivedTransformationPlansUrl,
archived: true
});
}
componentWillReceiveProps(nextProps) {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ describe('Overview component', () => {
);
});
test('starts when the component is mounted', () => {
expect(fetchTransformationPlansAction).toHaveBeenCalledTimes(1);
expect(fetchTransformationPlansAction).toHaveBeenCalled();
});

test('fetches transformation plan requests every 15 seconds', () => {
jest.advanceTimersByTime(15000);
expect(fetchTransformationPlansAction).toHaveBeenCalledTimes(2);
expect(fetchTransformationPlansAction).toHaveBeenCalledTimes(3);
});

// TODO: Come back to these once the UI is closer to final form
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Object {
"createTransformationPlanRequestAction": [Function],
"datastores": Array [],
"deleteInfrastructureMappingAction": [Function],
"fetchArchivedTransformationPlansUrl": "/api/dummyArchivedTransformationPlans",
"fetchClustersAction": [Function],
"fetchClustersUrl": "/api/dummyClusters",
"fetchDatastoresAction": [Function],
Expand All @@ -27,12 +28,14 @@ Object {
"hideDeleteConfirmationModalAction": [Function],
"hideMappingWizard": false,
"hidePlanWizard": false,
"isFetchingArchivedTransformationPlans": "true",
"isFetchingClusters": true,
"isFetchingDatastores": true,
"isFetchingNetworks": true,
"isFetchingProviders": true,
"isFetchingTransformationMappings": true,
"isFetchingTransformationPlans": true,
"isRejectedArchivedTransformationPlans": false,
"isRejectedClusters": false,
"isRejectedDatastores": false,
"isRejectedNetworks": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const initialState = Immutable({
fetchTransformationMappingsUrl: '/api/dummyMappings',
transformationPlans: [],
fetchTransformationPlansUrl: '/api/dummyTransformationPlans',
fetchArchivedTransformationPlansUrl: '/api/dummyArchivedTransformationPlans',
clusters: [],
isRejectedClusters: false,
isFetchingClusters: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class MappingWizard extends React.Component {

const currentStepProp = !onFinalStep && mappingWizardSteps[activeStepIndex];
const currentStepForm = !onFinalStep && this.props[currentStepProp];
const disableNextStep = !onFinalStep && !!currentStepForm.syncErrors;
const disableNextStep = !onFinalStep && (!!currentStepForm.syncErrors || !!currentStepForm.asyncErrors);

return (
<Wizard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,19 @@ class PlanWizard extends React.Component {
planWizardOptionsStep,
setPlansBodyAction,
setPlanScheduleAction,
setMigrationsFilterAction
setMigrationsFilterAction,
showAlertAction,
hideAlertAction
} = this.props;

if (activeStepIndex === 0) {
if (planWizardGeneralStep.asyncErrors) {
showAlertAction(sprintf(__('Name %s already exists'), planWizardGeneralStep.values.name));
return;
}
hideAlertAction();
}

if (activeStepIndex === 2) {
const plansBody = createMigrationPlans(planWizardGeneralStep, planWizardVMStep);

Expand Down Expand Up @@ -61,7 +71,10 @@ class PlanWizard extends React.Component {
planWizardExitedAction,
planWizardGeneralStep,
planWizardVMStep,
planWizardOptionsStep
planWizardOptionsStep,
alertText,
alertType,
hideAlertAction
} = this.props;

const { activeStepIndex, plansBody } = this.state;
Expand All @@ -73,7 +86,7 @@ class PlanWizard extends React.Component {
const currentStepForm = !onFinalStep && this.props[currentStepProp];

const disableNextStep =
(!onFinalStep && !!currentStepForm.syncErrors) ||
(!onFinalStep && (!!currentStepForm.syncErrors || !!currentStepForm.asyncErrors)) ||
(activeStepIndex === 1 &&
(!this.props.planWizardVMStep.values ||
!this.props.planWizardVMStep.values.selectedVms ||
Expand All @@ -94,6 +107,9 @@ class PlanWizard extends React.Component {
planWizardGeneralStep={planWizardGeneralStep}
planWizardVMStep={planWizardVMStep}
planWizardOptionsStep={planWizardOptionsStep}
alertText={alertText}
alertType={alertType}
hideAlertAction={hideAlertAction}
/>
</Wizard.Body>

Expand Down Expand Up @@ -128,7 +144,11 @@ PlanWizard.propTypes = {
setPlansBodyAction: PropTypes.func,
setPlanScheduleAction: PropTypes.func,
resetVmStepAction: PropTypes.func,
setMigrationsFilterAction: PropTypes.func
setMigrationsFilterAction: PropTypes.func,
showAlertAction: PropTypes.func,
hideAlertAction: PropTypes.func,
alertText: PropTypes.string,
alertType: PropTypes.string
};
PlanWizard.defaultProps = {
hidePlanWizard: true,
Expand All @@ -139,6 +159,10 @@ PlanWizard.defaultProps = {
planWizardOptionsStep: {},
setPlansBodyAction: noop,
setPlanScheduleAction: noop,
resetVmStepAction: noop
resetVmStepAction: noop,
showAlertAction: noop,
hideAlertAction: noop,
alertText: undefined,
alertType: undefined
};
export default PlanWizard;
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { reset } from 'redux-form';
import { HIDE_PLAN_WIZARD, PLAN_WIZARD_EXITED } from '../../OverviewConstants';
import { V2V_SET_PLANS_BODY, V2V_SET_PLAN_SCHEDULE } from './PlanWizardConstants';
import {
V2V_SET_PLANS_BODY,
V2V_SET_PLAN_SCHEDULE,
V2V_PLAN_WIZARD_SHOW_ALERT,
V2V_PLAN_WIZARD_HIDE_ALERT
} from './PlanWizardConstants';

import { V2V_VM_STEP_RESET } from './components/PlanWizardVMStep/PlanWizardVMStepConstants';

Expand Down Expand Up @@ -43,3 +48,16 @@ export const resetVmStepAction = () => dispatch => {
});
dispatch(reset('planWizardVMStep'));
};

export const showAlertAction = (alertText, alertType = 'error') => dispatch => {
dispatch({
type: V2V_PLAN_WIZARD_SHOW_ALERT,
payload: { alertText, alertType }
});
};

export const hideAlertAction = () => dispatch => {
dispatch({
type: V2V_PLAN_WIZARD_HIDE_ALERT
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class PlanWizardBody extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return JSON.stringify(this.props) !== JSON.stringify(nextProps);
}
componentWillUnmount() {
const { hideAlertAction } = this.props;
hideAlertAction();
}
render() {
return (
<ModalWizard.Body
Expand Down Expand Up @@ -58,7 +62,8 @@ PlanWizardBody.propTypes = {
disableNextStep: PropTypes.bool,
plansBody: PropTypes.object,
planWizardGeneralStep: PropTypes.object,
planWizardVMStep: PropTypes.object
planWizardVMStep: PropTypes.object,
hideAlertAction: PropTypes.func
};

PlanWizardBody.defaultProps = {
Expand All @@ -69,7 +74,8 @@ PlanWizardBody.defaultProps = {
disableNextStep: true,
plansBody: {},
planWizardGeneralStep: {},
planWizardVMStep: {}
planWizardVMStep: {},
hideAlertAction: noop
};

export default PlanWizardBody;
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
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';
export const V2V_PLAN_WIZARD_HIDE_ALERT = 'V2V_PLAN_WIZARD_HIDE_ALERT';
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Immutable from 'seamless-immutable';

import { V2V_SET_PLANS_BODY, V2V_SET_PLAN_SCHEDULE } from './PlanWizardConstants';
import {
V2V_SET_PLANS_BODY,
V2V_SET_PLAN_SCHEDULE,
V2V_PLAN_WIZARD_SHOW_ALERT,
V2V_PLAN_WIZARD_HIDE_ALERT
} from './PlanWizardConstants';

const initialState = Immutable({
plansBody: {}
Expand All @@ -12,7 +17,10 @@ export default (state = initialState, action) => {
return state.set('plansBody', action.payload);
case V2V_SET_PLAN_SCHEDULE:
return state.set('planSchedule', action.payload);

case V2V_PLAN_WIZARD_SHOW_ALERT:
return Immutable.merge(state, action.payload);
case V2V_PLAN_WIZARD_HIDE_ALERT:
return state.set('alertText', '');
default:
return state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

exports[`Plan Wizard integration test should mount the PlanWizard with mapStateToProps reduced 1`] = `
Object {
"alertText": undefined,
"alertType": undefined,
"hideAlertAction": [Function],
"hidePlanWizard": false,
"hidePlanWizardAction": [Function],
"planWizardExitedAction": [Function],
Expand All @@ -12,5 +15,6 @@ Object {
"setMigrationsFilterAction": [Function],
"setPlanScheduleAction": [Function],
"setPlansBodyAction": [Function],
"showAlertAction": [Function],
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Form } from 'patternfly-react';
import PropTypes from 'prop-types';
import { FormField } from '../../../../../common/forms/FormField';
import { BootstrapSelect } from '../../../../../common/forms/BootstrapSelect';
import { validation } from '../../../../../../../../common/constants'; // Oh my
import { validation } from '../../../../../../../../common/constants';
import { asyncValidate, onChange } from './helpers';

const PlanWizardGeneralStep = ({ transformationMappings }) => (
<Form className="form-horizontal">
Expand Down Expand Up @@ -70,5 +71,8 @@ PlanWizardGeneralStep.propTypes = {

export default reduxForm({
form: 'planWizardGeneralStep',
destroyOnUnmount: false
destroyOnUnmount: false,
asyncValidate,
asyncBlurFields: ['name'],
onChange
})(PlanWizardGeneralStep);
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const asyncValidate = (values, dispatch, props) =>
new Promise((resolve, reject) => {
const { name: newPlanName } = values;
const { transformationPlans, archivedTransformationPlans } = props;
const existingTransformationPlanNames = transformationPlans.reduce(
(names, plan) => [...names, plan.name.trim()],
[]
);
const existingArchivedPlanNames = archivedTransformationPlans.reduce(
(names, plan) => [...names, plan.name.trim()],
[]
);

const allPlanNames = [...existingTransformationPlanNames, ...existingArchivedPlanNames];
const duplicateName = allPlanNames.find(existingPlanName => existingPlanName === newPlanName.trim());

if (duplicateName) {
props.showAlertAction(sprintf(__('Name %s already exists'), newPlanName));
const error = { name: 'Please enter a unique name' };
reject(error);
} else {
resolve();
}
});

export const onChange = (values, dispatch, props) => {
if (props.valid) {
props.hideAlertAction();
}
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { connect } from 'react-redux';
import PlanWizardGeneralStep from './PlanWizardGeneralStep';
import { showAlertAction, hideAlertAction } from '../../PlanWizardActions';

const mapStateToProps = ({ overview }) => ({
transformationMappings: overview.transformationMappings,
transformationPlans: overview.transformationPlans,
archivedTransformationPlans: overview.archivedTransformationPlans,
initialValues: {
infrastructure_mapping: overview.planWizardId,
vm_choice_radio: 'vms_via_discovery'
}
});

export default connect(mapStateToProps)(PlanWizardGeneralStep);
const actions = { showAlertAction, hideAlertAction };

export default connect(
mapStateToProps,
actions
)(PlanWizardGeneralStep);
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const mapStateToProps = ({ overview, planWizard, form }, ownProps) => {
const selectedOverview = planWizardOverviewFilter(overview);
const selectedForms = planWizardFormFilter(form);
return {
...planWizard,
...selectedOverview,
...selectedForms,
...ownProps.data
Expand Down

0 comments on commit 594bd65

Please sign in to comment.