diff --git a/src/pages/Organizations/components/OrganizationForm.jsx b/src/pages/Organizations/components/OrganizationForm.jsx new file mode 100644 index 0000000000..5588f6c1cf --- /dev/null +++ b/src/pages/Organizations/components/OrganizationForm.jsx @@ -0,0 +1,182 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withRouter } from 'react-router-dom'; +import { Formik, Field } from 'formik'; +import { I18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Form, + FormGroup, +} from '@patternfly/react-core'; + +import { ConfigContext } from '../../../context'; +import FormField from '../../../components/FormField'; +import FormActionGroup from '../../../components/FormActionGroup'; +import AnsibleSelect from '../../../components/AnsibleSelect'; +import InstanceGroupsLookup from './InstanceGroupsLookup'; +import { required } from '../../../util/validators'; + +class OrganizationForm extends Component { + constructor (props) { + super(props); + + this.getRelatedInstanceGroups = this.getRelatedInstanceGroups.bind(this); + this.handleInstanceGroupsChange = this.handleInstanceGroupsChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + instanceGroups: [], + formIsValid: true, + }; + } + + async componentDidMount () { + let instanceGroups = []; + + if (!this.isEditingNewOrganization()) { + try { + instanceGroups = await this.getRelatedInstanceGroups(); + } catch (err) { + this.setState({ error: err }); + } + } + + this.setState({ + instanceGroups, + initialInstanceGroups: [...instanceGroups], + }); + } + + async getRelatedInstanceGroups () { + const { + api, + organization: { id } + } = this.props; + const { data } = await api.getOrganizationInstanceGroups(id); + return data.results; + } + + isEditingNewOrganization () { + const { organization } = this.props; + return !organization.id; + } + + handleInstanceGroupsChange (instanceGroups) { + this.setState({ instanceGroups }); + } + + async handleSubmit (values) { + const { handleSubmit } = this.props; + const { instanceGroups, initialInstanceGroups } = this.state; + + const initialIds = initialInstanceGroups.map(ig => ig.id); + const updatedIds = instanceGroups.map(ig => ig.id); + const groupsToAssociate = [...updatedIds] + .filter(x => !initialIds.includes(x)); + const groupsToDisassociate = [...initialIds] + .filter(x => !updatedIds.includes(x)); + + handleSubmit(values, groupsToAssociate, groupsToDisassociate); + } + + render () { + const { api, organization, handleCancel } = this.props; + const { instanceGroups, formIsValid, error } = this.state; + const defaultVenv = '/venv/ansible/'; + + return ( + + {({ i18n }) => ( + ( +
+
+ + + + {({ custom_virtualenvs }) => ( + custom_virtualenvs && custom_virtualenvs.length > 1 && ( + ( + + + + )} + /> + ) + )} + +
+ + + {error ?
error
: null} + + )} + /> + )} +
+ ); + } +} + +OrganizationForm.propTypes = { + api: PropTypes.shape().isRequired, + organization: PropTypes.shape(), + handleSubmit: PropTypes.func.isRequired, + handleCancel: PropTypes.func.isRequired, +}; + +OrganizationForm.defaultProps = { + organization: { + name: '', + description: '', + custom_virtualenv: '', + } +}; + +OrganizationForm.contextTypes = { + custom_virtualenvs: PropTypes.arrayOf(PropTypes.string) +}; + +export default withRouter(OrganizationForm); diff --git a/src/pages/Organizations/screens/Organization/OrganizationEdit.jsx b/src/pages/Organizations/screens/Organization/OrganizationEdit.jsx index 38d1c255bc..f4de4309d0 100644 --- a/src/pages/Organizations/screens/Organization/OrganizationEdit.jsx +++ b/src/pages/Organizations/screens/Organization/OrganizationEdit.jsx @@ -1,82 +1,30 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; -import { Formik, Field } from 'formik'; -import { I18n, i18nMark } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { - CardBody, - Form, - FormGroup, -} from '@patternfly/react-core'; +import { CardBody } from '@patternfly/react-core'; -import { ConfigContext } from '../../../../context'; -import FormField from '../../../../components/FormField'; -import FormActionGroup from '../../../../components/FormActionGroup'; -import AnsibleSelect from '../../../../components/AnsibleSelect'; -import InstanceGroupsLookup from '../../components/InstanceGroupsLookup'; - -function required (message) { - return value => { - if (!value.trim()) { - return message || i18nMark('This field must not be blank'); - } - return undefined; - }; -} +import OrganizationForm from '../../components/OrganizationForm'; class OrganizationEdit extends Component { constructor (props) { super(props); - this.getRelatedInstanceGroups = this.getRelatedInstanceGroups.bind(this); - this.handleInstanceGroupsChange = this.handleInstanceGroupsChange.bind(this); + // this.getRelatedInstanceGroups = this.getRelatedInstanceGroups.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.postInstanceGroups = this.postInstanceGroups.bind(this); this.handleCancel = this.handleCancel.bind(this); this.handleSuccess = this.handleSuccess.bind(this); this.state = { - initialInstanceGroups: [], - instanceGroups: [], error: '', - formIsValid: true, }; } - async componentDidMount () { - let instanceGroups; - try { - instanceGroups = await this.getRelatedInstanceGroups(); - } catch (err) { - this.setState({ error: err }); - } - - this.setState({ - instanceGroups, - initialInstanceGroups: instanceGroups, - }); - } - - async getRelatedInstanceGroups () { - const { - api, - organization: { id } - } = this.props; - const { data } = await api.getOrganizationInstanceGroups(id); - return data.results; - } - - handleInstanceGroupsChange (instanceGroups) { - this.setState({ instanceGroups }); - } - - async handleSubmit (values) { + async handleSubmit (values, groupsToAssociate, groupsToDisassociate) { const { api, organization } = this.props; - const { instanceGroups } = this.state; try { await api.updateOrganizationDetails(organization.id, values); - await this.postInstanceGroups(instanceGroups); + await this.postInstanceGroups(groupsToAssociate, groupsToDisassociate); } catch (err) { this.setState({ error: err }); } finally { @@ -94,19 +42,10 @@ class OrganizationEdit extends Component { history.push(`/organizations/${id}`); } - async postInstanceGroups (instanceGroups) { + async postInstanceGroups (groupsToAssociate, groupsToDisassociate) { const { api, organization } = this.props; - const { initialInstanceGroups } = this.state; const url = organization.related.instance_groups; - const initialIds = initialInstanceGroups.map(ig => ig.id); - const updatedIds = instanceGroups.map(ig => ig.id); - - const groupsToAssociate = [...updatedIds] - .filter(x => !initialIds.includes(x)); - const groupsToDisassociate = [...initialIds] - .filter(x => !updatedIds.includes(x)); - try { await Promise.all(groupsToAssociate.map(async id => { await api.associateInstanceGroup(url, id); @@ -121,91 +60,27 @@ class OrganizationEdit extends Component { render () { const { api, organization } = this.props; - const { - instanceGroups, - formIsValid, - error, - } = this.state; - const defaultVenv = '/venv/ansible/'; + const { error } = this.state; return ( - - {({ i18n }) => ( - ( -
-
- - - - {({ custom_virtualenvs }) => ( - custom_virtualenvs && custom_virtualenvs.length > 1 && ( - ( - - - - )} - /> - ) - )} - -
- - - { error ?
error
: '' } - - )} - /> - )} -
+ + {error ?
error
: null}
); } } +OrganizationEdit.propTypes = { + api: PropTypes.shape().isRequired, + organization: PropTypes.shape().isRequired, +}; + OrganizationEdit.contextTypes = { custom_virtualenvs: PropTypes.arrayOf(PropTypes.string) }; diff --git a/src/pages/Organizations/screens/OrganizationAdd.jsx b/src/pages/Organizations/screens/OrganizationAdd.jsx index f00e118b1c..ec69bbf9be 100644 --- a/src/pages/Organizations/screens/OrganizationAdd.jsx +++ b/src/pages/Organizations/screens/OrganizationAdd.jsx @@ -1,26 +1,19 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; import { I18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { PageSection, - Form, - FormGroup, - TextInput, - Gallery, Card, CardHeader, CardBody, Button, Tooltip, } from '@patternfly/react-core'; -import { QuestionCircleIcon, TimesIcon } from '@patternfly/react-icons'; +import { TimesIcon } from '@patternfly/react-icons'; -import { ConfigContext } from '../../../context'; -import AnsibleSelect from '../../../components/AnsibleSelect'; -import FormActionGroup from '../../../components/FormActionGroup'; -import InstanceGroupsLookup from '../components/InstanceGroupsLookup'; +import OrganizationForm from '../components/OrganizationForm'; class OrganizationAdd extends React.Component { constructor (props) { @@ -33,12 +26,7 @@ class OrganizationAdd extends React.Component { this.handleSuccess = this.handleSuccess.bind(this); this.state = { - name: '', - description: '', - custom_virtualenv: '', - instanceGroups: [], error: '', - defaultEnv: '/venv/ansible/', }; } @@ -50,23 +38,15 @@ class OrganizationAdd extends React.Component { this.setState({ [targetName]: val }); } - async handleSubmit () { + async handleSubmit (values, groupsToAssociate) { const { api } = this.props; - const { name, description, custom_virtualenv, instanceGroups } = this.state; - const data = { - name, - description, - custom_virtualenv - }; try { - const { data: response } = await api.createOrganization(data); + const { data: response } = await api.createOrganization(values); const instanceGroupsUrl = response.related.instance_groups; try { - if (instanceGroups.length > 0) { - instanceGroups.forEach(async (select) => { - await api.associateInstanceGroup(instanceGroupsUrl, select.id); - }); - } + await Promise.all(groupsToAssociate.map(async id => { + await api.associateInstanceGroup(instanceGroupsUrl, id); + })); } catch (err) { this.setState({ error: err }); } finally { @@ -89,15 +69,7 @@ class OrganizationAdd extends React.Component { render () { const { api } = this.props; - const { - name, - description, - custom_virtualenv, - defaultEnv, - instanceGroups, - error - } = this.state; - const enabled = name.length > 0; // TODO: add better form validation + const { error } = this.state; return ( @@ -119,72 +91,12 @@ class OrganizationAdd extends React.Component { -
- - - - - - - - - - {({ custom_virtualenvs }) => ( - custom_virtualenvs && custom_virtualenvs.length > 1 && ( - - {i18n._(t`Ansible Environment`)} - {' '} - - - - - )} - fieldId="add-org-custom-virtualenv" - > - - - ) - )} - - - - {error ?
error
: ''} - + + {error ?
error
: ''}
)} @@ -194,6 +106,10 @@ class OrganizationAdd extends React.Component { } } +OrganizationAdd.propTypes = { + api: PropTypes.shape().isRequired, +}; + OrganizationAdd.contextTypes = { custom_virtualenvs: PropTypes.arrayOf(PropTypes.string) }; diff --git a/src/util/validators.js b/src/util/validators.js new file mode 100644 index 0000000000..4e0f95e25b --- /dev/null +++ b/src/util/validators.js @@ -0,0 +1,19 @@ +import { i18nMark } from '@lingui/react'; + +export function required (message) { + return value => { + if (!value.trim()) { + return message || i18nMark('This field must not be blank'); + } + return undefined; + }; +} + +export function maxLength (max) { + return value => { + if (value.trim() > max) { + return i18nMark(`This field must not exceed ${max} characters`); + } + return undefined; + }; +}