Submit select roles
* Change FormErrorList to render standard alert * Add SelectRolesForm validation * Add actions for selecting roles and handling zaqar message Implements: blueprint tripleo-ui-select-roles Depends-On: I42d66da37dcb07c1be112623d89769377bb23284 Change-Id: I1305b5cb1522ca18f03c4c5113b822a86aff5d97
This commit is contained in:
parent
e1adf4c78e
commit
5cde2a4f79
6
releasenotes/notes/select_roles-e4aed7695f9ba45d.yaml
Normal file
6
releasenotes/notes/select_roles-e4aed7695f9ba45d.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Custom deployment roles selection. Roles section of Deployment plan page now
|
||||
includes 'Manage Roles'. Clicking the link provides a list of all available
|
||||
roles and lets user select roles intended for deployment.
|
@ -95,6 +95,12 @@ export default {
|
||||
dispatch(RolesActions.fetchAvailableRolesFinished(payload, history));
|
||||
break;
|
||||
}
|
||||
// TODO(jtomasek): change this back once underlining tripleo-common patch is fixed
|
||||
case MistralConstants.SELECT_ROLES: {
|
||||
// case 'tripleo.roles.v1.select_roles': {
|
||||
dispatch(RolesActions.selectRolesFinished(payload, history));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
@ -26,7 +26,7 @@ const AvailableRoleInput = ({
|
||||
input: { value, name, onChange },
|
||||
style
|
||||
}) => (
|
||||
<Col xs={6} sm={4} md={3} lg={2} style={style}>
|
||||
<Col xs={12} sm={4} lg={3} style={style}>
|
||||
<div
|
||||
className={cx(
|
||||
'card-pf card-pf-view card-pf-view-select card-pf-view-multi-select',
|
||||
|
@ -20,6 +20,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ModalHeader, ModalTitle } from 'react-bootstrap';
|
||||
import React from 'react';
|
||||
import { pickBy } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import AvailableRoleInput from './AvailableRoleInput';
|
||||
@ -46,10 +47,16 @@ class SelectRolesDialog extends React.Component {
|
||||
this.props.fetchAvailableRoles();
|
||||
}
|
||||
|
||||
handleFormSubmit = (values, dispatch, formProps) => {
|
||||
const roleNames = Object.keys(pickBy(values));
|
||||
this.props.selectRoles(this.props.currentPlanName, roleNames);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
availableRoles,
|
||||
availableRolesLoaded,
|
||||
fetchingAvailableRoles,
|
||||
intl: { formatMessage },
|
||||
currentPlanName,
|
||||
roles
|
||||
@ -62,28 +69,31 @@ class SelectRolesDialog extends React.Component {
|
||||
<FormattedMessage {...messages.selectRolesTitle} />
|
||||
</ModalTitle>
|
||||
</ModalHeader>
|
||||
<SelectRolesForm
|
||||
initialValues={availableRoles.map(r => roles.includes(r)).toJS()}
|
||||
currentPlanName={currentPlanName}
|
||||
<Loader
|
||||
height={100}
|
||||
loaded={availableRolesLoaded && !fetchingAvailableRoles}
|
||||
content={formatMessage(messages.loadingAvailableRoles)}
|
||||
>
|
||||
<Loader
|
||||
loaded={availableRolesLoaded}
|
||||
content={formatMessage(messages.loadingAvailableRoles)}
|
||||
<SelectRolesForm
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={availableRoles
|
||||
.map((_, key) => roles.keySeq().includes(key))
|
||||
.toJS()}
|
||||
availableRoles={availableRoles}
|
||||
currentPlanName={currentPlanName}
|
||||
>
|
||||
<div className="row row-cards-pf">
|
||||
{availableRoles
|
||||
.toList()
|
||||
.map(role => (
|
||||
<Field
|
||||
component={AvailableRoleInput}
|
||||
role={role}
|
||||
name={role.name}
|
||||
key={role.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Loader>
|
||||
</SelectRolesForm>
|
||||
{availableRoles
|
||||
.toList()
|
||||
.map(role => (
|
||||
<Field
|
||||
component={AvailableRoleInput}
|
||||
role={role}
|
||||
name={role.name}
|
||||
key={role.name}
|
||||
/>
|
||||
))}
|
||||
</SelectRolesForm>
|
||||
</Loader>
|
||||
</RoutedModal>
|
||||
);
|
||||
}
|
||||
@ -93,20 +103,25 @@ SelectRolesDialog.propTypes = {
|
||||
availableRolesLoaded: PropTypes.bool.isRequired,
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
fetchAvailableRoles: PropTypes.func.isRequired,
|
||||
fetchingAvailableRoles: PropTypes.bool.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
roles: ImmutablePropTypes.map.isRequired
|
||||
roles: ImmutablePropTypes.map.isRequired,
|
||||
selectRoles: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
availableRoles: getMergedRoles(state),
|
||||
availableRolesLoaded: state.availableRoles.loaded,
|
||||
fetchingAvailableRoles: state.availableRoles.isFetching,
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
roles: getRoles(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchAvailableRoles: planName =>
|
||||
dispatch(RolesActions.fetchAvailableRoles(planName))
|
||||
dispatch(RolesActions.fetchAvailableRoles(planName)),
|
||||
selectRoles: (planName, roleNames) =>
|
||||
dispatch(RolesActions.selectRoles(planName, roleNames))
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
|
@ -16,37 +16,61 @@
|
||||
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { ModalBody, ModalFooter } from 'react-bootstrap';
|
||||
import { ModalFooter } from 'react-bootstrap';
|
||||
import { pickBy } from 'lodash';
|
||||
import { OverlayLoader } from '../ui/Loader';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
|
||||
import { CloseModalButton } from '../ui/Modals';
|
||||
import FormErrorList from '../ui/forms/FormErrorList';
|
||||
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
|
||||
|
||||
const messages = defineMessages({
|
||||
saveChanges: {
|
||||
id: 'SelectRolesDialog.saveChanges',
|
||||
id: 'SelectRolesForm.saveChanges',
|
||||
defaultMessage: 'Save Changes'
|
||||
},
|
||||
cancel: {
|
||||
id: 'SelectRolesDialog.cancel',
|
||||
id: 'SelectRolesForm.cancel',
|
||||
defaultMessage: 'Cancel'
|
||||
},
|
||||
updatingRoles: {
|
||||
id: 'SelectRolesForm.updatingRoles',
|
||||
defaultMessage: 'Updating Roles...'
|
||||
},
|
||||
primaryRoleValidationError: {
|
||||
id: 'SelectRolesForm.primaryRoleValidationError',
|
||||
defaultMessage:
|
||||
'Please select one role tagged as "primary" and "controller"'
|
||||
}
|
||||
});
|
||||
|
||||
class SelectRolesForm extends React.Component {
|
||||
render() {
|
||||
const { children, error, handleSubmit, invalid, pristine } = this.props;
|
||||
const {
|
||||
children,
|
||||
error,
|
||||
handleSubmit,
|
||||
invalid,
|
||||
intl: { formatMessage },
|
||||
pristine,
|
||||
submitting
|
||||
} = this.props;
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ModalBody>
|
||||
<FormErrorList errors={error ? [error] : []} />
|
||||
{children}
|
||||
</ModalBody>
|
||||
<OverlayLoader
|
||||
loaded={!submitting}
|
||||
content={formatMessage(messages.updatingRoles)}
|
||||
>
|
||||
<ModalFormErrorList errors={error ? [error] : []} />
|
||||
<div className="cards-pf">
|
||||
<div className="row row-cards-pf">{children}</div>
|
||||
</div>
|
||||
</OverlayLoader>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
disabled={invalid || pristine}
|
||||
disabled={invalid || pristine || submitting}
|
||||
bsStyle="primary"
|
||||
type="submit"
|
||||
>
|
||||
@ -65,14 +89,29 @@ SelectRolesForm.propTypes = {
|
||||
currentPlanName: PropTypes.string.isRequired,
|
||||
error: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
pristine: PropTypes.bool.isRequired
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
submitting: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const validateForm = (values, { availableRoles }) => {
|
||||
const errors = {};
|
||||
const selectedRoleNames = Object.keys(pickBy(values));
|
||||
const selectedRoles = availableRoles.filter((r, k) =>
|
||||
selectedRoleNames.includes(k)
|
||||
);
|
||||
if (!selectedRoles.some(r => r.tags.includes('primary'))) {
|
||||
errors._error = {
|
||||
message: <FormattedMessage {...messages.primaryRoleValidationError} />
|
||||
};
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
const form = reduxForm({
|
||||
enableReinitialize: true,
|
||||
form: 'selectRoles',
|
||||
keepDirtyOnReinitialize: true
|
||||
validate: validateForm
|
||||
});
|
||||
|
||||
export default injectIntl(form(SelectRolesForm));
|
||||
|
@ -38,8 +38,7 @@ export default class FormErrorList extends React.Component {
|
||||
} else {
|
||||
return (
|
||||
<p>
|
||||
<strong>{errors[0].title}</strong>
|
||||
<br />
|
||||
{errors[0].title && <strong>{errors[0].title}</strong>}{' '}
|
||||
{errors[0].message}
|
||||
</p>
|
||||
);
|
||||
@ -51,10 +50,7 @@ export default class FormErrorList extends React.Component {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="toast-pf form-error-list alert alert-danger"
|
||||
role="alert"
|
||||
>
|
||||
<div className="form-error-list alert alert-danger" role="alert">
|
||||
<span className="pficon pficon-error-circle-o" aria-hidden="true" />
|
||||
{this.renderErrors()}
|
||||
</div>
|
||||
|
@ -35,6 +35,7 @@ export default {
|
||||
PLAN_EXPORT: 'tripleo.plan_management.v1.export_deployment_plan',
|
||||
ROLE_LIST: 'tripleo.role.list',
|
||||
LIST_AVAILABLE_ROLES: 'tripleo.plan_management.v1.list_available_roles',
|
||||
SELECT_ROLES: 'tripleo.plan_management.v1.select_roles',
|
||||
VALIDATIONS_LIST: 'tripleo.validations.list_validations',
|
||||
VALIDATIONS_RUN: 'tripleo.validations.v1.run_validation',
|
||||
VALIDATIONS_RUN_GROUPS: 'tripleo.validations.v1.run_groups'
|
||||
|
@ -22,5 +22,6 @@ export default keyMirror({
|
||||
FETCH_AVAILABLE_ROLES_FAILED: null,
|
||||
FETCH_ROLES_PENDING: null,
|
||||
FETCH_ROLES_SUCCESS: null,
|
||||
FETCH_ROLES_FAILED: null
|
||||
FETCH_ROLES_FAILED: null,
|
||||
SELECT_ROLES_SUCCESS: null
|
||||
});
|
||||
|
@ -60,6 +60,7 @@ const rolesReducer = (state = initialState, action) => {
|
||||
case RolesConstants.FETCH_ROLES_PENDING:
|
||||
return state.set('isFetching', true);
|
||||
|
||||
case RolesConstants.SELECT_ROLES_SUCCESS:
|
||||
case RolesConstants.FETCH_ROLES_SUCCESS: {
|
||||
const roles = fromJS(action.payload).map(role =>
|
||||
new Role(role).update(role =>
|
||||
@ -79,6 +80,9 @@ const rolesReducer = (state = initialState, action) => {
|
||||
.set('isFetching', false)
|
||||
.set('loaded', true);
|
||||
|
||||
case RolesConstants.SELECT_ROLES_SUCCESS:
|
||||
return state.set('roles', roles);
|
||||
|
||||
case PlansConstants.PLAN_CHOSEN:
|
||||
return state.set('loaded', false);
|
||||
|
||||
|
@ -30,6 +30,6 @@ form.form, form.form-horizontal {
|
||||
}
|
||||
}
|
||||
|
||||
.form-error-list.toast-pf {
|
||||
display: block;
|
||||
.form-error-list {
|
||||
margin-top: 20px;
|
||||
}
|
@ -49,7 +49,6 @@
|
||||
border-bottom: 1px solid @modal-header-border-color ;
|
||||
}
|
||||
|
||||
|
||||
.modal-footer {
|
||||
margin-top: 0px;
|
||||
background-color: #f8f8f8;
|
||||
@ -59,7 +58,7 @@
|
||||
|
||||
.modal-form-error-list {
|
||||
&:extend(.modal-header, .modal .modal-header);
|
||||
.toast-pf.form-error-list {
|
||||
margin-bottom: 0;
|
||||
.form-error-list {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user