Fix EnvironmentConfiguration dialog
* convert Environment Configuration form to redux-form * add required environments validation * add HorizontalCheckBox and EnvironmentCheckBox * Fix the bug where error is not visible when it is happening in other topic tab by adding general error message * actions and reducer update Partial-Bug: 1743722 Change-Id: Ia056621a847b56913b1dfff57f6e8237f62bd8d7
This commit is contained in:
parent
4e776a6291
commit
8ee595b019
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Improvements in Deployment configuration -> Overal Settings, Improved
|
||||
performance, form now shows general error in case when some sub section
|
||||
contains error
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import { startSubmit, stopSubmit } from 'redux-form';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
|
||||
import EnvironmentConfigurationActions from '../../js/actions/EnvironmentConfigurationActions';
|
||||
@ -128,7 +129,7 @@ describe('updateEnvironmentConfiguration', () => {
|
||||
.then(() => {
|
||||
expect(MistralApiService.runAction).toHaveBeenCalled();
|
||||
expect(store.getActions()).toEqual([
|
||||
EnvironmentConfigurationActions.updateEnvironmentConfigurationPending(),
|
||||
startSubmit('environmentConfigurationForm'),
|
||||
EnvironmentConfigurationActions.updateEnvironmentConfigurationSuccess(
|
||||
[
|
||||
'overcloud-resource-registry-puppet.yaml',
|
||||
@ -136,17 +137,9 @@ describe('updateEnvironmentConfiguration', () => {
|
||||
'environments/network-isolation.yaml'
|
||||
]
|
||||
),
|
||||
stopSubmit('environmentConfigurationForm'),
|
||||
NotificationActions.notify({ type: 'NOTIFY' })
|
||||
]);
|
||||
});
|
||||
|
||||
// const result = EnvironmentConfigurationActions.updateEnvironmentConfiguration(
|
||||
// 'myPlan'
|
||||
// );
|
||||
// const mockDispatch = jest.fn();
|
||||
// result(mockDispatch, jest.fn(), mockGetIntl).then(() => {
|
||||
// console.log(mockDispatch);
|
||||
// expect(mockDispatch.mock.calls).toEqual(true);
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { normalize } from 'normalizr';
|
||||
import { startSubmit, stopSubmit } from 'redux-form';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import EnvironmentConfigurationConstants from '../constants/EnvironmentConfigurationConstants';
|
||||
@ -35,6 +36,10 @@ const messages = defineMessages({
|
||||
envConfigUpdatedNotificationTitle: {
|
||||
id: 'EnvironmentConfigurationActions.envConfigUpdatedNotificationTitle',
|
||||
defaultMessage: 'Environment Configuration updated'
|
||||
},
|
||||
configurationNotUpdatedError: {
|
||||
id: 'EnvironmentConfigurationActions.configurationNotUpdatedError',
|
||||
defaultMessage: 'Deployment configuration could not be updated'
|
||||
}
|
||||
});
|
||||
|
||||
@ -85,10 +90,10 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
updateEnvironmentConfiguration(planName, data, formFields) {
|
||||
updateEnvironmentConfiguration(planName, data, redirect) {
|
||||
return (dispatch, getState, { getIntl }) => {
|
||||
const { formatMessage } = getIntl(getState());
|
||||
dispatch(this.updateEnvironmentConfigurationPending());
|
||||
dispatch(startSubmit('environmentConfigurationForm'));
|
||||
return dispatch(
|
||||
MistralApiService.runAction(MistralConstants.CAPABILITIES_UPDATE, {
|
||||
environments: data,
|
||||
@ -98,6 +103,7 @@ export default {
|
||||
.then(response => {
|
||||
const enabledEnvs = response.environments.map(env => env.path);
|
||||
dispatch(this.updateEnvironmentConfigurationSuccess(enabledEnvs));
|
||||
dispatch(stopSubmit('environmentConfigurationForm'));
|
||||
dispatch(
|
||||
NotificationActions.notify({
|
||||
title: formatMessage(messages.envConfigUpdatedNotificationTitle),
|
||||
@ -107,34 +113,21 @@ export default {
|
||||
type: 'success'
|
||||
})
|
||||
);
|
||||
redirect && redirect();
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(
|
||||
handleErrors(
|
||||
error,
|
||||
'Deployment configuration could not be updated',
|
||||
false
|
||||
)
|
||||
);
|
||||
dispatch(
|
||||
this.updateEnvironmentConfigurationFailed([
|
||||
{
|
||||
title: 'Configuration could not be updated',
|
||||
stopSubmit('environmentConfigurationForm', {
|
||||
_error: {
|
||||
title: formatMessage(messages.configurationNotUpdatedError),
|
||||
message: error.message
|
||||
}
|
||||
])
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
updateEnvironmentConfigurationPending() {
|
||||
return {
|
||||
type:
|
||||
EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_PENDING
|
||||
};
|
||||
},
|
||||
|
||||
updateEnvironmentConfigurationSuccess(enabledEnvironments) {
|
||||
return {
|
||||
type:
|
||||
@ -143,17 +136,6 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
updateEnvironmentConfigurationFailed(formErrors = [], formFieldErrors = {}) {
|
||||
return {
|
||||
type:
|
||||
EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_FAILED,
|
||||
payload: {
|
||||
formErrors,
|
||||
formFieldErrors
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
fetchEnvironment(planName, environmentPath) {
|
||||
return dispatch => {
|
||||
dispatch(this.fetchEnvironmentPending(environmentPath));
|
||||
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import cx from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Checkbox, Col, FormGroup } from 'react-bootstrap';
|
||||
|
||||
import {
|
||||
getValidationState,
|
||||
InputDescription,
|
||||
InputMessage
|
||||
} from '../ui/reduxForm/utils';
|
||||
|
||||
/**
|
||||
* EnvironmentCheckBox differs from HorizontalCheckBox in being considered as
|
||||
* always touched
|
||||
*/
|
||||
const EnvironmentCheckBox = ({
|
||||
id,
|
||||
label,
|
||||
labelColumns,
|
||||
inputColumns,
|
||||
description,
|
||||
type,
|
||||
input,
|
||||
meta,
|
||||
required,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<FormGroup
|
||||
controlId={id}
|
||||
validationState={getValidationState({ ...meta, touched: true })}
|
||||
>
|
||||
<Col smOffset={labelColumns} sm={inputColumns}>
|
||||
<Checkbox {...input} {...rest}>
|
||||
<span className={cx({ 'required-pf': required })}>{label}</span>
|
||||
</Checkbox>
|
||||
<InputMessage {...meta} touched />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
EnvironmentCheckBox.propTypes = {
|
||||
description: PropTypes.node,
|
||||
id: PropTypes.string.isRequired,
|
||||
input: PropTypes.object.isRequired,
|
||||
inputColumns: PropTypes.number.isRequired,
|
||||
label: PropTypes.node,
|
||||
labelColumns: PropTypes.number.isRequired,
|
||||
meta: PropTypes.object.isRequired,
|
||||
required: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
};
|
||||
EnvironmentCheckBox.defaultProps = {
|
||||
labelColumns: 5,
|
||||
inputColumns: 7,
|
||||
required: false,
|
||||
type: 'text'
|
||||
};
|
||||
export default EnvironmentCheckBox;
|
@ -14,40 +14,26 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import { camelCase, mapKeys } from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import Formsy, { addValidationRule } from 'formsy-react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import { CloseModalButton } from '../ui/Modals';
|
||||
import EnvironmentConfigurationActions from '../../actions/EnvironmentConfigurationActions';
|
||||
import EnvironmentConfigurationForm from './EnvironmentConfigurationForm';
|
||||
import EnvironmentConfigurationSidebar from './EnvironmentConfigurationSidebar';
|
||||
import EnvironmentConfigurationTopic from './EnvironmentConfigurationTopic';
|
||||
import { getCurrentPlanName } from '../../selectors/plans';
|
||||
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
|
||||
import {
|
||||
getEnvironments,
|
||||
getTopicsTree
|
||||
} from '../../selectors/environmentConfiguration';
|
||||
import { Loader } from '../ui/Loader';
|
||||
import Tab from '../ui/Tab';
|
||||
import TabPane from '../ui/TabPane';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: {
|
||||
id: 'EnvironmentConfiguration.cancel',
|
||||
defaultMessage: 'Cancel'
|
||||
},
|
||||
saveChanges: {
|
||||
id: 'EnvironmentConfiguration.saveChanges',
|
||||
defaultMessage: 'Save Changes'
|
||||
},
|
||||
saveAndClose: {
|
||||
id: 'EnvironmentConfiguration.saveAndClose',
|
||||
defaultMessage: 'Save And Close'
|
||||
},
|
||||
loadingEnvironmentConfiguration: {
|
||||
id: 'EnvironmentConfiguration.loadingEnvironmentConfiguration',
|
||||
defaultMessage: 'Loading Deployment Configuration...'
|
||||
@ -58,8 +44,6 @@ class EnvironmentConfiguration extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
canSubmit: false,
|
||||
closeOnSubmit: false,
|
||||
activeTab: undefined
|
||||
};
|
||||
}
|
||||
@ -75,165 +59,95 @@ class EnvironmentConfiguration extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.invalidateForm(this.props.formFieldErrors.toJS());
|
||||
}
|
||||
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
}
|
||||
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
}
|
||||
|
||||
invalidateForm(formFieldErrors) {
|
||||
this.refs.environmentConfigurationForm.updateInputsWithError(
|
||||
formFieldErrors
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Formsy splits data into objects by '.', file names include '.'
|
||||
* so we need to convert data back to e.g. { filename.yaml: true, ... }
|
||||
*/
|
||||
_convertFormData(formData) {
|
||||
return _.mapValues(
|
||||
_.mapKeys(formData, (value, key) => {
|
||||
return key + '.yaml';
|
||||
}),
|
||||
value => {
|
||||
return value.yaml;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleSubmit(formData, resetForm, invalidateForm) {
|
||||
const data = this._convertFormData(formData);
|
||||
this.disableButton();
|
||||
this.props.updateEnvironmentConfiguration(
|
||||
this.props.currentPlanName,
|
||||
data,
|
||||
Object.keys(this.refs.environmentConfigurationForm.inputs)
|
||||
);
|
||||
|
||||
if (this.state.closeOnSubmit) {
|
||||
this.setState({
|
||||
closeOnSubmit: false
|
||||
});
|
||||
|
||||
this.props.history.push(`/plans/${this.props.currentPlanName}`);
|
||||
}
|
||||
}
|
||||
|
||||
onSubmitAndClose() {
|
||||
this.setState(
|
||||
{
|
||||
closeOnSubmit: true
|
||||
},
|
||||
this.refs.environmentConfigurationForm.submit
|
||||
);
|
||||
}
|
||||
|
||||
activateTab(tabName, e) {
|
||||
e.preventDefault();
|
||||
this.setState({ activeTab: tabName });
|
||||
}
|
||||
|
||||
isTabActive(tabName) {
|
||||
let firstTabName = _.camelCase(
|
||||
isTabActive = tabName => {
|
||||
const firstTabName = camelCase(
|
||||
this.props.environmentConfigurationTopics.first().get('title')
|
||||
);
|
||||
let currentTab = this.state.activeTab || firstTabName;
|
||||
const currentTab = this.state.activeTab || firstTabName;
|
||||
return currentTab === tabName;
|
||||
};
|
||||
|
||||
renderTopics = () => {
|
||||
const { environmentConfigurationTopics } = this.props;
|
||||
return environmentConfigurationTopics.toList().map((topic, index) => {
|
||||
const tabName = camelCase(topic.get('title'));
|
||||
return (
|
||||
<TabPane
|
||||
isActive={this.isTabActive(tabName)}
|
||||
key={index}
|
||||
renderOnlyActive
|
||||
>
|
||||
<EnvironmentConfigurationTopic
|
||||
title={topic.get('title')}
|
||||
description={topic.get('description')}
|
||||
environmentGroups={topic.get('environment_groups')}
|
||||
/>
|
||||
</TabPane>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
handleSubmit = ({ saveAndClose, ...values }, dispatch, props) => {
|
||||
const data = mapKeys(values, (_, k) => k.replace(':', '.'));
|
||||
const {
|
||||
currentPlanName,
|
||||
history,
|
||||
updateEnvironmentConfiguration
|
||||
} = this.props;
|
||||
if (saveAndClose) {
|
||||
updateEnvironmentConfiguration(currentPlanName, data, () =>
|
||||
history.push(`/plans/${currentPlanName}`)
|
||||
);
|
||||
} else {
|
||||
updateEnvironmentConfiguration(currentPlanName, data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initial values are all enabled environments, keys are changed as dots
|
||||
* in input names cause unwanted splitting into nested objects
|
||||
*/
|
||||
getFormInitialValues() {
|
||||
return this.props.allEnvironments
|
||||
.mapKeys(k => k.replace('.', ':'))
|
||||
.filter(e => e.enabled)
|
||||
.map(e => e.enabled)
|
||||
.toJS();
|
||||
}
|
||||
|
||||
render() {
|
||||
let topics = this.props.environmentConfigurationTopics
|
||||
.toList()
|
||||
.map((topic, index) => {
|
||||
let tabName = _.camelCase(topic.get('title'));
|
||||
return (
|
||||
<TabPane isActive={this.isTabActive(tabName)} key={index}>
|
||||
<EnvironmentConfigurationTopic
|
||||
key={index}
|
||||
title={topic.get('title')}
|
||||
description={topic.get('description')}
|
||||
allEnvironments={this.props.allEnvironments}
|
||||
environmentGroups={topic.get('environment_groups')}
|
||||
/>
|
||||
</TabPane>
|
||||
);
|
||||
});
|
||||
|
||||
let topicTabs = this.props.environmentConfigurationTopics
|
||||
.toList()
|
||||
.map((topic, index) => {
|
||||
let tabName = _.camelCase(topic.get('title'));
|
||||
return (
|
||||
<Tab key={index} isActive={this.isTabActive(tabName)}>
|
||||
<a href="" onClick={this.activateTab.bind(this, tabName)}>
|
||||
{topic.get('title')}
|
||||
</a>
|
||||
</Tab>
|
||||
);
|
||||
});
|
||||
const {
|
||||
allEnvironments,
|
||||
environmentConfigurationTopics,
|
||||
isFetching,
|
||||
intl: { formatMessage }
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Formsy
|
||||
ref="environmentConfigurationForm"
|
||||
role="form"
|
||||
className="form"
|
||||
onSubmit={this.handleSubmit.bind(this)}
|
||||
onValid={this.enableButton.bind(this)}
|
||||
onInvalid={this.disableButton.bind(this)}
|
||||
<Loader
|
||||
height={60}
|
||||
loaded={!isFetching}
|
||||
content={formatMessage(messages.loadingEnvironmentConfiguration)}
|
||||
>
|
||||
<Loader
|
||||
height={60}
|
||||
loaded={!this.props.isFetching}
|
||||
content={this.props.intl.formatMessage(
|
||||
messages.loadingEnvironmentConfiguration
|
||||
)}
|
||||
<EnvironmentConfigurationForm
|
||||
allEnvironments={allEnvironments}
|
||||
onSubmit={this.handleSubmit}
|
||||
initialValues={this.getFormInitialValues()}
|
||||
>
|
||||
<ModalFormErrorList errors={this.props.formErrors.toJS()} />
|
||||
<div className="container-fluid">
|
||||
<div className="row row-eq-height">
|
||||
<div className="col-sm-4 sidebar-pf sidebar-pf-left">
|
||||
<ul
|
||||
id="DeploymentConfiguration__CategoriesList"
|
||||
className="nav nav-pills nav-stacked nav-arrows"
|
||||
>
|
||||
{topicTabs}
|
||||
</ul>
|
||||
</div>
|
||||
<EnvironmentConfigurationSidebar
|
||||
activateTab={tabName => this.setState({ activeTab: tabName })}
|
||||
categories={environmentConfigurationTopics.toList().toJS()}
|
||||
isTabActive={this.isTabActive}
|
||||
/>
|
||||
<div className="col-sm-8">
|
||||
<div className="tab-content">{topics}</div>
|
||||
<div className="tab-content">{this.renderTopics()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Loader>
|
||||
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!this.state.canSubmit}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
<FormattedMessage {...messages.saveChanges} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!this.state.canSubmit}
|
||||
onClick={this.onSubmitAndClose.bind(this)}
|
||||
className="btn btn-default"
|
||||
>
|
||||
<FormattedMessage {...messages.saveAndClose} />
|
||||
</button>
|
||||
<CloseModalButton>
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</CloseModalButton>
|
||||
</div>
|
||||
</Formsy>
|
||||
</EnvironmentConfigurationForm>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -252,60 +166,35 @@ EnvironmentConfiguration.propTypes = {
|
||||
updateEnvironmentConfiguration: PropTypes.func
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
allEnvironments: getEnvironments(state),
|
||||
environmentConfigurationTopics: getTopicsTree(state),
|
||||
formErrors: state.environmentConfiguration.getIn(['form', 'formErrors']),
|
||||
formFieldErrors: state.environmentConfiguration.getIn([
|
||||
'form',
|
||||
'formFieldErrors'
|
||||
]),
|
||||
isFetching: state.environmentConfiguration.isFetching
|
||||
};
|
||||
}
|
||||
const mapStateToProps = state => ({
|
||||
currentPlanName: getCurrentPlanName(state),
|
||||
allEnvironments: getEnvironments(state),
|
||||
environmentConfigurationTopics: getTopicsTree(state),
|
||||
formErrors: state.environmentConfiguration.getIn(['form', 'formErrors']),
|
||||
formFieldErrors: state.environmentConfiguration.getIn([
|
||||
'form',
|
||||
'formFieldErrors'
|
||||
]),
|
||||
isFetching: state.environmentConfiguration.isFetching
|
||||
});
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
fetchEnvironmentConfiguration: planName => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(planName)
|
||||
);
|
||||
},
|
||||
updateEnvironmentConfiguration: (planName, data, inputFields) => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.updateEnvironmentConfiguration(
|
||||
planName,
|
||||
data,
|
||||
inputFields
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchEnvironmentConfiguration: planName => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.fetchEnvironmentConfiguration(planName)
|
||||
);
|
||||
},
|
||||
updateEnvironmentConfiguration: (planName, data, inputFields) => {
|
||||
dispatch(
|
||||
EnvironmentConfigurationActions.updateEnvironmentConfiguration(
|
||||
planName,
|
||||
data,
|
||||
inputFields
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default injectIntl(
|
||||
connect(mapStateToProps, mapDispatchToProps)(EnvironmentConfiguration)
|
||||
);
|
||||
|
||||
/**
|
||||
* requiresEnvironments validation
|
||||
* Invalidates input if it is selected and environment it requires is not.
|
||||
* example: validations="requiredEnvironments:['some_environment.yaml']"
|
||||
*/
|
||||
addValidationRule('requiredEnvironments', function(
|
||||
values,
|
||||
value,
|
||||
requiredEnvironmentFieldNames
|
||||
) {
|
||||
if (value) {
|
||||
return !_.filter(
|
||||
_.values(_.pick(values, requiredEnvironmentFieldNames)),
|
||||
function(val) {
|
||||
return val === false;
|
||||
}
|
||||
).length;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright 2018 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Button, Form, ModalFooter } from 'react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import { pickBy } from 'lodash';
|
||||
|
||||
import { CloseModalButton } from '../ui/Modals';
|
||||
import ModalFormErrorList from '../ui/forms/ModalFormErrorList';
|
||||
import { OverlayLoader } from '../ui/Loader';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: {
|
||||
id: 'EnvironmentConfigurationForm.cancel',
|
||||
defaultMessage: 'Cancel'
|
||||
},
|
||||
saveChanges: {
|
||||
id: 'EnvironmentConfigurationForm.saveChanges',
|
||||
defaultMessage: 'Save Changes'
|
||||
},
|
||||
saveAndClose: {
|
||||
id: 'EnvironmentConfigurationForm.saveAndClose',
|
||||
defaultMessage: 'Save And Close'
|
||||
},
|
||||
requiredEnvironments: {
|
||||
id: 'EnvironmentConfigurationForm.requiredEnvironments',
|
||||
defaultMessage: 'This option requires {requiredEnvironments} to be enabled.'
|
||||
},
|
||||
missingConfiguration: {
|
||||
id: 'EnvironmentConfigurationForm.missingConfiguration',
|
||||
defaultMessage: 'Missing configuration',
|
||||
description:
|
||||
'Title for general error message describing dependent environments need to be enabled'
|
||||
},
|
||||
requiredEnvironmentsGlobalError: {
|
||||
id: 'EnvironmentConfigurationForm.requiredEnvironmentGlobalError',
|
||||
defaultMessage:
|
||||
'Selected options depend on other options which are not enabled',
|
||||
description:
|
||||
'General error message describing dependent environments need to be enabled'
|
||||
},
|
||||
updatingEnvironmentConfiguration: {
|
||||
id: 'EnvironmentConfigurationForm.updatingEnvironmentConfiguration',
|
||||
defaultMessage: 'Updating Environment configuration'
|
||||
}
|
||||
});
|
||||
|
||||
const EnvironmentConfigurationForm = ({
|
||||
error,
|
||||
children,
|
||||
onSubmit,
|
||||
handleSubmit,
|
||||
intl: { formatMessage },
|
||||
invalid,
|
||||
pristine,
|
||||
submitting,
|
||||
initialValues
|
||||
}) => (
|
||||
<Form onSubmit={handleSubmit} horizontal>
|
||||
<OverlayLoader
|
||||
loaded={!submitting}
|
||||
content={formatMessage(messages.updatingEnvironmentConfiguration)}
|
||||
>
|
||||
<ModalFormErrorList errors={error ? [error] : []} />
|
||||
{children}
|
||||
</OverlayLoader>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={invalid || pristine || submitting}
|
||||
bsStyle="primary"
|
||||
>
|
||||
<FormattedMessage {...messages.saveChanges} />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={invalid || pristine || submitting}
|
||||
onClick={handleSubmit(values =>
|
||||
onSubmit({ ...values, saveAndClose: true })
|
||||
)}
|
||||
>
|
||||
<FormattedMessage {...messages.saveAndClose} />
|
||||
</Button>
|
||||
<CloseModalButton>
|
||||
<FormattedMessage {...messages.cancel} />
|
||||
</CloseModalButton>
|
||||
</ModalFooter>
|
||||
</Form>
|
||||
);
|
||||
EnvironmentConfigurationForm.propTypes = {
|
||||
children: PropTypes.node,
|
||||
error: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
submitting: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const validate = (values, { allEnvironments, intl: { formatMessage } }) => {
|
||||
const errors = {};
|
||||
|
||||
// Get array of environment files currently enabled in the form
|
||||
const enabledValues = Object.keys(pickBy(values)).map(v =>
|
||||
v.replace(':', '.')
|
||||
);
|
||||
|
||||
// For each enabled environment, get its list of required environments. Add
|
||||
// error if some of them are not enabled
|
||||
enabledValues.map(e => {
|
||||
const requires = allEnvironments.getIn([e, 'requires']);
|
||||
|
||||
if (
|
||||
!requires
|
||||
.toSet()
|
||||
.subtract(enabledValues)
|
||||
.isEmpty()
|
||||
) {
|
||||
const requiredEnvironmentNames = requires
|
||||
.map(env => allEnvironments.getIn([env, 'title'], env))
|
||||
.toArray();
|
||||
|
||||
errors[e.replace('.', ':')] = formatMessage(
|
||||
messages.requiredEnvironments,
|
||||
{
|
||||
requiredEnvironments: requiredEnvironmentNames
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Add global error message
|
||||
if (Object.keys(errors).length > 0) {
|
||||
errors['_error'] = {
|
||||
title: formatMessage(messages.missingConfiguration),
|
||||
message: formatMessage(messages.requiredEnvironmentsGlobalError)
|
||||
};
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
const form = reduxForm({
|
||||
form: 'environmentConfigurationForm',
|
||||
validate
|
||||
});
|
||||
|
||||
export default injectIntl(form(EnvironmentConfigurationForm));
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2018 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { camelCase } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Tab from '../ui/Tab';
|
||||
|
||||
const EnvironmentConfigurationSidebar = ({
|
||||
activateTab,
|
||||
categories,
|
||||
isTabActive
|
||||
}) => (
|
||||
<div className="col-sm-4 sidebar-pf sidebar-pf-left">
|
||||
<ul
|
||||
id="DeploymentConfiguration__CategoriesList"
|
||||
className="nav nav-pills nav-stacked nav-arrows"
|
||||
>
|
||||
{categories.map(({ title }, index) => {
|
||||
const tabName = camelCase(title);
|
||||
return (
|
||||
<Tab key={index} isActive={isTabActive(tabName)}>
|
||||
<a className="link" onClick={() => activateTab(tabName)}>
|
||||
{title}
|
||||
</a>
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
EnvironmentConfigurationSidebar.propTypes = {
|
||||
activateTab: PropTypes.func.isRequired,
|
||||
categories: PropTypes.array.isRequired,
|
||||
isTabActive: PropTypes.func.isRequired
|
||||
};
|
||||
export default EnvironmentConfigurationSidebar;
|
@ -20,39 +20,30 @@ import React from 'react';
|
||||
|
||||
import EnvironmentGroup from './EnvironmentGroup';
|
||||
|
||||
export default class EnvironmentConfigurationTopic extends React.Component {
|
||||
render() {
|
||||
let environmentGroups = this.props.environmentGroups
|
||||
const EnvironmentConfigurationTopic = ({ description, environmentGroups }) => (
|
||||
<fieldset className="environment-topic">
|
||||
{description && (
|
||||
<p>
|
||||
<i>{description}</i>
|
||||
</p>
|
||||
)}
|
||||
{environmentGroups
|
||||
.toList()
|
||||
.map((envGroup, index) => {
|
||||
return (
|
||||
<EnvironmentGroup
|
||||
key={index}
|
||||
title={envGroup.get('title')}
|
||||
description={envGroup.get('description')}
|
||||
allEnvironments={this.props.allEnvironments}
|
||||
environments={envGroup.get('environments')}
|
||||
mutuallyExclusive={envGroup.get('mutually_exclusive')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const { description } = this.props;
|
||||
return (
|
||||
<fieldset className="environment-topic">
|
||||
{description && (
|
||||
<p>
|
||||
<i>{description}</i>
|
||||
</p>
|
||||
)}
|
||||
{environmentGroups}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
.map((envGroup, index) => (
|
||||
<EnvironmentGroup
|
||||
key={index}
|
||||
title={envGroup.get('title')}
|
||||
description={envGroup.get('description')}
|
||||
environments={envGroup.get('environments')}
|
||||
mutuallyExclusive={envGroup.get('mutually_exclusive')}
|
||||
/>
|
||||
))}
|
||||
</fieldset>
|
||||
);
|
||||
EnvironmentConfigurationTopic.propTypes = {
|
||||
allEnvironments: ImmutablePropTypes.map.isRequired,
|
||||
description: PropTypes.string,
|
||||
environmentGroups: ImmutablePropTypes.list,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default EnvironmentConfigurationTopic;
|
||||
|
@ -14,117 +14,61 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { change, Field } from 'redux-form';
|
||||
|
||||
import GenericCheckBox from '../ui/forms/GenericCheckBox';
|
||||
import GroupedCheckBox from '../ui/forms/GroupedCheckBox';
|
||||
|
||||
const messages = defineMessages({
|
||||
requiredEnvironments: {
|
||||
id: 'EnvironmentGroup.requiredEnvironments',
|
||||
defaultMessage: 'This option requires {requiredEnvironments} to be enabled.'
|
||||
}
|
||||
});
|
||||
import EnvironmentGroupHeading from './EnvironmentGroupHeading';
|
||||
import EnvironmentCheckBox from './EnvironmentCheckBox';
|
||||
|
||||
class EnvironmentGroup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
checkedEnvironment: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const firstCheckedEnvironment = this.props.environments
|
||||
.filter(env => env.get('enabled') === true)
|
||||
.first();
|
||||
this.setState({
|
||||
checkedEnvironment: firstCheckedEnvironment
|
||||
? firstCheckedEnvironment.get('file')
|
||||
: null
|
||||
});
|
||||
}
|
||||
|
||||
onGroupedCheckBoxChange(checked, environmentFile) {
|
||||
this.setState({ checkedEnvironment: checked ? environmentFile : null });
|
||||
}
|
||||
|
||||
getRequiredEnvironmentsNames(environment) {
|
||||
return environment.requires
|
||||
.map(env => this.props.allEnvironments.getIn([env, 'title'], env))
|
||||
.toArray();
|
||||
}
|
||||
|
||||
generateInputs() {
|
||||
const {
|
||||
environments,
|
||||
intl: { formatMessage },
|
||||
mutuallyExclusive
|
||||
} = this.props;
|
||||
|
||||
return environments.toList().map((environment, index) => {
|
||||
const requiredEnvironments = environment.requires.toArray();
|
||||
const requiredEnvironmentNames = this.getRequiredEnvironmentsNames(
|
||||
environment
|
||||
);
|
||||
if (mutuallyExclusive) {
|
||||
let checkBoxValue = this.state.checkedEnvironment === environment.file;
|
||||
return (
|
||||
<GroupedCheckBox
|
||||
key={environment.file}
|
||||
name={environment.file}
|
||||
id={environment.file}
|
||||
title={environment.title}
|
||||
value={checkBoxValue}
|
||||
validations={{ requiredEnvironments: requiredEnvironments }}
|
||||
validationError={formatMessage(messages.requiredEnvironments, {
|
||||
requiredEnvironments: requiredEnvironmentNames
|
||||
})}
|
||||
onChange={this.onGroupedCheckBoxChange.bind(this)}
|
||||
description={environment.description}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<GenericCheckBox
|
||||
key={environment.file}
|
||||
name={environment.file}
|
||||
id={environment.file}
|
||||
title={environment.title}
|
||||
value={environment.get('enabled', false)}
|
||||
validations={{ requiredEnvironments: requiredEnvironments }}
|
||||
validationError={formatMessage(messages.requiredEnvironments, {
|
||||
requiredEnvironments: requiredEnvironmentNames
|
||||
})}
|
||||
description={environment.description}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* When enabling environment in mutually exclusive group disable other
|
||||
* environments in the group
|
||||
*/
|
||||
handleEnablingEnvironment = (event, newValue, previousValue) => {
|
||||
const { changeValue, environments } = this.props;
|
||||
if (newValue) {
|
||||
environments
|
||||
.delete(event.target.name.replace(':', '.'))
|
||||
.map(env => changeValue(env.file.replace('.', ':'), false));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let environments = this.generateInputs();
|
||||
|
||||
const { title, description, environments, mutuallyExclusive } = this.props;
|
||||
return (
|
||||
<div className="environment-group">
|
||||
<EnvironmentGroupHeading
|
||||
title={this.props.title}
|
||||
description={this.props.description}
|
||||
/>
|
||||
{environments}
|
||||
<EnvironmentGroupHeading title={title} description={description} />
|
||||
{environments
|
||||
.toList()
|
||||
.map((environment, index) => (
|
||||
<Field
|
||||
labelColumns={0}
|
||||
inputColumns={12}
|
||||
id={environment.file}
|
||||
key={environment.file}
|
||||
label={environment.title}
|
||||
title={environment.file}
|
||||
description={environment.description}
|
||||
name={environment.file.replace('.', ':')}
|
||||
component={EnvironmentCheckBox}
|
||||
type="checkbox"
|
||||
onChange={
|
||||
mutuallyExclusive ? this.handleEnablingEnvironment : null
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
EnvironmentGroup.propTypes = {
|
||||
allEnvironments: ImmutablePropTypes.map.isRequired,
|
||||
changeValue: PropTypes.func.isRequired,
|
||||
description: PropTypes.string,
|
||||
environments: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
mutuallyExclusive: PropTypes.bool.isRequired,
|
||||
title: PropTypes.string
|
||||
};
|
||||
@ -132,26 +76,9 @@ EnvironmentGroup.defaultProps = {
|
||||
mutuallyExclusive: false
|
||||
};
|
||||
|
||||
export default injectIntl(EnvironmentGroup);
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeValue: (field, value) =>
|
||||
dispatch(change('environmentConfigurationForm', field, value))
|
||||
});
|
||||
|
||||
class EnvironmentGroupHeading extends React.Component {
|
||||
render() {
|
||||
if (this.props.title) {
|
||||
return (
|
||||
<h4>
|
||||
{this.props.title}
|
||||
<br />
|
||||
<small>{this.props.description}</small>
|
||||
</h4>
|
||||
);
|
||||
} else if (this.props.description) {
|
||||
return <p>{this.props.description}</p>;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
EnvironmentGroupHeading.propTypes = {
|
||||
description: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
};
|
||||
export default connect(null, mapDispatchToProps)(EnvironmentGroup);
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
const EnvironmentGroupHeading = ({ title, description }) => {
|
||||
if (title) {
|
||||
return (
|
||||
<h4>
|
||||
{title}
|
||||
<br />
|
||||
<small>{description}</small>
|
||||
</h4>
|
||||
);
|
||||
} else if (description) {
|
||||
return <p>{description}</p>;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
EnvironmentGroupHeading.propTypes = {
|
||||
description: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
export default EnvironmentGroupHeading;
|
@ -22,7 +22,11 @@ import React from 'react';
|
||||
import InputDescription from './InputDescription';
|
||||
import InputErrorMessage from './InputErrorMessage';
|
||||
|
||||
class GenericCheckBox extends React.Component {
|
||||
class GenericCheckBox extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
}
|
||||
changeValue(event) {
|
||||
this.props.setValue(event.target.checked);
|
||||
}
|
||||
@ -43,7 +47,7 @@ class GenericCheckBox extends React.Component {
|
||||
name={this.props.name}
|
||||
ref={this.props.id}
|
||||
id={this.props.id}
|
||||
onChange={this.changeValue.bind(this)}
|
||||
onChange={this.changeValue}
|
||||
checked={!!this.props.getValue()}
|
||||
value={this.props.getValue()}
|
||||
/>
|
||||
|
@ -22,7 +22,11 @@ import React from 'react';
|
||||
import InputDescription from './InputDescription';
|
||||
import InputErrorMessage from './InputErrorMessage';
|
||||
|
||||
class GroupedCheckBox extends React.Component {
|
||||
class GroupedCheckBox extends React.PureComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.changeValue = this.changeValue.bind(this);
|
||||
}
|
||||
changeValue(event) {
|
||||
this.props.onChange(event.target.checked, this.props.name);
|
||||
this.props.setValue(event.target.checked);
|
||||
@ -44,7 +48,7 @@ class GroupedCheckBox extends React.Component {
|
||||
name={this.props.name}
|
||||
ref={this.props.id}
|
||||
id={this.props.id}
|
||||
onChange={this.changeValue.bind(this)}
|
||||
onChange={this.changeValue}
|
||||
checked={!!this.props.getValue()}
|
||||
value={this.props.getValue()}
|
||||
/>
|
||||
|
65
src/js/components/ui/reduxForm/HorizontalCheckBox.js
Normal file
65
src/js/components/ui/reduxForm/HorizontalCheckBox.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright 2017 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import cx from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { Checkbox, Col, FormGroup } from 'react-bootstrap';
|
||||
|
||||
import { getValidationState, InputDescription, InputMessage } from './utils';
|
||||
|
||||
const HorizontalCheckBox = ({
|
||||
id,
|
||||
label,
|
||||
labelColumns,
|
||||
inputColumns,
|
||||
description,
|
||||
type,
|
||||
input,
|
||||
meta,
|
||||
required,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<FormGroup controlId={id} validationState={getValidationState(meta)}>
|
||||
<Col smOffset={labelColumns} sm={inputColumns}>
|
||||
<Checkbox {...input} {...rest}>
|
||||
<span className={cx({ 'required-pf': required })}>{label}</span>
|
||||
</Checkbox>
|
||||
<InputMessage {...meta} />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
HorizontalCheckBox.propTypes = {
|
||||
description: PropTypes.node,
|
||||
id: PropTypes.string.isRequired,
|
||||
input: PropTypes.object.isRequired,
|
||||
inputColumns: PropTypes.number.isRequired,
|
||||
label: PropTypes.node,
|
||||
labelColumns: PropTypes.number.isRequired,
|
||||
meta: PropTypes.object.isRequired,
|
||||
required: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
};
|
||||
HorizontalCheckBox.defaultProps = {
|
||||
labelColumns: 5,
|
||||
inputColumns: 7,
|
||||
required: false,
|
||||
type: 'text'
|
||||
};
|
||||
export default HorizontalCheckBox;
|
@ -44,7 +44,7 @@ const HorizontalInput = ({
|
||||
</Col>
|
||||
<Col sm={inputColumns}>
|
||||
<FormControl type={type} {...input} {...rest} />
|
||||
<InputMessage fieldMeta={meta} />
|
||||
<InputMessage {...meta} />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
@ -52,7 +52,7 @@ const HorizontalSelect = ({
|
||||
>
|
||||
{children}
|
||||
</FormControl>
|
||||
<InputMessage fieldMeta={meta} />
|
||||
<InputMessage {...meta} />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
@ -43,7 +43,7 @@ const HorizontalTextarea = ({
|
||||
</Col>
|
||||
<Col sm={inputColumns}>
|
||||
<FormControl componentClass="textarea" {...input} {...rest} />
|
||||
<InputMessage fieldMeta={meta} />
|
||||
<InputMessage {...meta} />
|
||||
<InputDescription description={description} />
|
||||
</Col>
|
||||
</FormGroup>
|
||||
|
@ -35,12 +35,11 @@ InputDescription.propTypes = {
|
||||
description: PropTypes.node
|
||||
};
|
||||
|
||||
export const InputMessage = ({ fieldMeta: { touched, error, warning } }) =>
|
||||
export const InputMessage = ({ touched, error, warning }) =>
|
||||
touched
|
||||
? (error ? <HelpBlock>{error}</HelpBlock> : null) ||
|
||||
(warning ? <HelpBlock>{warning}</HelpBlock> : null)
|
||||
: null;
|
||||
|
||||
InputMessage.propTypes = {
|
||||
error: PropTypes.node,
|
||||
touched: PropTypes.bool,
|
||||
|
@ -48,9 +48,6 @@ export default function environmentConfigurationReducer(
|
||||
case EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_CONFIGURATION_FAILED:
|
||||
return state.set('isFetching', false).set('loaded', true);
|
||||
|
||||
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_PENDING:
|
||||
return state.set('isFetching', true);
|
||||
|
||||
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_SUCCESS: {
|
||||
const enabledEnvs = fromJS(action.payload);
|
||||
const updatedEnvs = state.environments.map(environment => {
|
||||
@ -59,15 +56,9 @@ export default function environmentConfigurationReducer(
|
||||
enabledEnvs.includes(environment.get('file'))
|
||||
);
|
||||
});
|
||||
return state.set('environments', updatedEnvs).set('isFetching', false);
|
||||
return state.set('environments', updatedEnvs);
|
||||
}
|
||||
|
||||
case EnvironmentConfigurationConstants.UPDATE_ENVIRONMENT_CONFIGURATION_FAILED:
|
||||
return state
|
||||
.set('isFetching', false)
|
||||
.set('loaded', true)
|
||||
.set('form', fromJS(action.payload));
|
||||
|
||||
case EnvironmentConfigurationConstants.FETCH_ENVIRONMENT_PENDING:
|
||||
return state.setIn(['environments', action.payload, 'isFetching'], true);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user