From 91b8aa90ff94cfa25a49f5f08e4e1d04351a082d Mon Sep 17 00:00:00 2001 From: catjones9 Date: Mon, 10 Jun 2019 16:54:05 -0400 Subject: [PATCH] Added 'Max Hosts' field in the Add/Edit Organization view * max hosts field is enabled is user is superuser, otherwise it is disabled and default is 0 * OrganizationForm tests added for max hosts input * minMaxValue added in validators to validate user input for max hosts Signed-off-by: catjones9 --- .../components/OrganizationForm.test.jsx | 106 +++++++++++++++++- .../Organization/OrganizationDetail.test.jsx | 7 +- .../components/OrganizationForm.jsx | 74 +++++------- .../Organization/OrganizationDetail.jsx | 2 +- .../screens/Organization/OrganizationEdit.jsx | 16 ++- .../Organizations/screens/OrganizationAdd.jsx | 14 ++- src/util/validators.jsx | 2 +- 7 files changed, 160 insertions(+), 61 deletions(-) diff --git a/__tests__/pages/Organizations/components/OrganizationForm.test.jsx b/__tests__/pages/Organizations/components/OrganizationForm.test.jsx index e1a4e84048..640845ed20 100644 --- a/__tests__/pages/Organizations/components/OrganizationForm.test.jsx +++ b/__tests__/pages/Organizations/components/OrganizationForm.test.jsx @@ -8,11 +8,16 @@ jest.mock('../../../../src/api'); describe('', () => { const network = {}; - + const meConfig = { + me: { + is_superuser: false + } + }; const mockData = { id: 1, name: 'Foo', description: 'Bar', + max_hosts: 1, custom_virtualenv: 'Fizz', related: { instance_groups: '/api/v2/organizations/1/instance_groups' @@ -30,6 +35,7 @@ describe('', () => { organization={mockData} handleSubmit={jest.fn()} handleCancel={jest.fn()} + me={meConfig.me} /> ), { context: { network }, @@ -55,6 +61,7 @@ describe('', () => { organization={mockData} handleSubmit={jest.fn()} handleCancel={jest.fn()} + me={meConfig.me} /> ), { context: { network }, @@ -72,6 +79,7 @@ describe('', () => { organization={mockData} handleSubmit={jest.fn()} handleCancel={jest.fn()} + me={meConfig.me} /> ); @@ -98,6 +106,7 @@ describe('', () => { organization={mockData} handleSubmit={jest.fn()} handleCancel={jest.fn()} + me={meConfig.me} /> ); @@ -110,6 +119,10 @@ describe('', () => { target: { value: 'new bar', name: 'description' } }); expect(form.state('values').description).toEqual('new bar'); + wrapper.find('input#org-max_hosts').simulate('change', { + target: { value: '134', name: 'max_hosts' } + }); + expect(form.state('values').max_hosts).toEqual('134'); }); test('AnsibleSelect component renders if there are virtual environments', () => { @@ -122,6 +135,7 @@ describe('', () => { organization={mockData} handleSubmit={jest.fn()} handleCancel={jest.fn()} + me={meConfig.me} /> ), { context: { config }, @@ -138,6 +152,7 @@ describe('', () => { organization={mockData} handleSubmit={handleSubmit} handleCancel={jest.fn()} + me={meConfig.me} /> ); expect(handleSubmit).not.toHaveBeenCalled(); @@ -146,6 +161,7 @@ describe('', () => { expect(handleSubmit).toHaveBeenCalledWith({ name: 'Foo', description: 'Bar', + max_hosts: 1, custom_virtualenv: 'Fizz', }, [], []); }); @@ -163,6 +179,7 @@ describe('', () => { const mockDataForm = { name: 'Foo', description: 'Bar', + max_hosts: 1, custom_virtualenv: 'Fizz', }; const handleSubmit = jest.fn(); @@ -175,14 +192,13 @@ describe('', () => { organization={mockData} handleSubmit={handleSubmit} handleCancel={jest.fn()} + me={meConfig.me} /> ), { - context: { network }, + context: { network } } ); - await sleep(0); - wrapper.find('InstanceGroupsLookup').prop('onChange')([ { name: 'One', id: 1 }, { name: 'Three', id: 3 } @@ -193,13 +209,95 @@ describe('', () => { expect(handleSubmit).toHaveBeenCalledWith(mockDataForm, [3], [2]); }); + test('handleSubmit is called with max_hosts value if it is in range', async () => { + const handleSubmit = jest.fn(); + + // normal mount + const wrapper = mountWithContexts( + + ); + wrapper.find('button[aria-label="Save"]').simulate('click'); + await sleep(0); + expect(handleSubmit).toHaveBeenCalledWith({ + name: 'Foo', + description: 'Bar', + max_hosts: 1, + custom_virtualenv: 'Fizz', + }, [], []); + }); + + test('handleSubmit does not get called if max_hosts value is out of range', async () => { + const handleSubmit = jest.fn(); + + // not mount with Negative value + const mockDataNegative = JSON.parse(JSON.stringify(mockData)); + mockDataNegative.max_hosts = -5; + const wrapper1 = mountWithContexts( + + ); + wrapper1.find('button[aria-label="Save"]').simulate('click'); + await sleep(0); + expect(handleSubmit).not.toHaveBeenCalled(); + + // not mount with Out of Range value + const mockDataOoR = JSON.parse(JSON.stringify(mockData)); + mockDataOoR.max_hosts = 999999999999; + const wrapper2 = mountWithContexts( + + ); + wrapper2.find('button[aria-label="Save"]').simulate('click'); + await sleep(0); + expect(handleSubmit).not.toHaveBeenCalled(); + }); + + test('handleSubmit is called and max_hosts value defaults to 0 if input is not a number', async () => { + const handleSubmit = jest.fn(); + + // mount with String value (default to zero) + const mockDataString = JSON.parse(JSON.stringify(mockData)); + mockDataString.max_hosts = 'Bee'; + const wrapper = mountWithContexts( + + ); + wrapper.find('button[aria-label="Save"]').simulate('click'); + await sleep(0); + expect(handleSubmit).toHaveBeenCalledWith({ + name: 'Foo', + description: 'Bar', + max_hosts: 0, + custom_virtualenv: 'Fizz', + }, [], []); + }); + test('calls "handleCancel" when Cancel button is clicked', () => { const handleCancel = jest.fn(); + const wrapper = mountWithContexts( ); expect(handleCancel).not.toHaveBeenCalled(); diff --git a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx index a4f70f11da..ca7676db42 100644 --- a/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx +++ b/__tests__/pages/Organizations/screens/Organization/OrganizationDetail.test.jsx @@ -10,6 +10,7 @@ describe('', () => { name: 'Foo', description: 'Bar', custom_virtualenv: 'Fizz', + max_hosts: '0', created: 'Bat', modified: 'Boo', summary_fields: { @@ -71,11 +72,12 @@ describe('', () => { ); const detailWrapper = wrapper.find('Detail'); - expect(detailWrapper.length).toBe(5); + expect(detailWrapper.length).toBe(6); const nameDetail = detailWrapper.findWhere(node => node.props().label === 'Name'); const descriptionDetail = detailWrapper.findWhere(node => node.props().label === 'Description'); const custom_virtualenvDetail = detailWrapper.findWhere(node => node.props().label === 'Ansible Environment'); + const max_hostsDetail = detailWrapper.findWhere(node => node.props().label === 'Max Hosts'); const createdDetail = detailWrapper.findWhere(node => node.props().label === 'Created'); const modifiedDetail = detailWrapper.findWhere(node => node.props().label === 'Last Modified'); expect(nameDetail.find('dt').text()).toBe('Name'); @@ -92,6 +94,9 @@ describe('', () => { expect(modifiedDetail.find('dt').text()).toBe('Last Modified'); expect(modifiedDetail.find('dd').text()).toBe('Boo'); + + expect(max_hostsDetail.find('dt').text()).toBe('Max Hosts'); + expect(max_hostsDetail.find('dd').text()).toBe('0'); }); test('should show edit button for users with edit permission', () => { diff --git a/src/pages/Organizations/components/OrganizationForm.jsx b/src/pages/Organizations/components/OrganizationForm.jsx index 1411bc2b4e..5e99e7d484 100644 --- a/src/pages/Organizations/components/OrganizationForm.jsx +++ b/src/pages/Organizations/components/OrganizationForm.jsx @@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom'; import { Formik, Field } from 'formik'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; + import { Tooltip, Form, @@ -19,7 +20,7 @@ import FormField from '../../../components/FormField'; import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup'; import AnsibleSelect from '../../../components/AnsibleSelect'; import InstanceGroupsLookup from './InstanceGroupsLookup'; - +import { OrganizationsAPI } from '../../../api'; import { required, minMaxValue } from '../../../util/validators'; class OrganizationForm extends Component { @@ -28,9 +29,7 @@ class OrganizationForm extends Component { this.getRelatedInstanceGroups = this.getRelatedInstanceGroups.bind(this); this.handleInstanceGroupsChange = this.handleInstanceGroupsChange.bind(this); - this.maxHostsChange = this.maxHostsChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); - this.readUsers = this.readUsers.bind(this); this.state = { instanceGroups: [], @@ -64,14 +63,6 @@ class OrganizationForm extends Component { return data.results; } - async readUsers (queryParams) { - const { api } = this.props; - console.log(api.readUsers((queryParams, is_superuser))); - console.log(api.readUsers((queryParams))); - return true; - // return api.readUsers((queryParams)); - } - isEditingNewOrganization () { const { organization } = this.props; return !organization.id; @@ -81,10 +72,6 @@ class OrganizationForm extends Component { this.setState({ instanceGroups }); } - maxHostsChange (event) { - console.log('boop'); - } - handleSubmit (values) { const { handleSubmit } = this.props; const { instanceGroups, initialInstanceGroups } = this.state; @@ -96,23 +83,25 @@ class OrganizationForm extends Component { const groupsToDisassociate = [...initialIds] .filter(x => !updatedIds.includes(x)); + if (typeof values.max_hosts !== 'number' || values.max_hosts === 'undefined') { + values.max_hosts = 0; + } + handleSubmit(values, groupsToAssociate, groupsToDisassociate); } render () { - const { organization, handleCancel, i18n, is_superuser } = this.props; + const { organization, handleCancel, i18n, me } = this.props; const { instanceGroups, formIsValid, error } = this.state; const defaultVenv = '/venv/ansible/'; - console.log(organization); - return ( ( @@ -136,25 +125,26 @@ class OrganizationForm extends Component { id="org-max_hosts" name="max_hosts" type="number" - label={ - {i18n._(t`Max Hosts`)} + label={ + ( + + {i18n._(t`Max Hosts`)} {' '} {( - - - - ) - } - } + + + + )} + + ) + } validate={minMaxValue(0, 2147483647, i18n)} - onChange={(evt) => this.maxHostsChange(evt)} - // isDisabled={!is_superuser + console.log(is_superuser)} - // isDisabled={this.readUsers} - isDisabled={this.readUsers? true: false} - /> + me={me || {}} + isDisabled={!me.is_superuser} + /> {({ custom_virtualenvs }) => ( custom_virtualenvs && custom_virtualenvs.length > 1 && ( @@ -197,20 +187,14 @@ class OrganizationForm extends Component { } FormField.propTypes = { - //consider changing this in FormField.jsx, as many fields may need tooltips in the label - label: PropTypes.oneOfType ([ - PropTypes.object, - PropTypes.string - ]) -} - -console.log() + label: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired +}; OrganizationForm.propTypes = { organization: PropTypes.shape(), handleSubmit: PropTypes.func.isRequired, handleCancel: PropTypes.func.isRequired, - }; +}; OrganizationForm.defaultProps = { organization: { @@ -218,7 +202,7 @@ OrganizationForm.defaultProps = { description: '', max_hosts: '0', custom_virtualenv: '', - } + }, }; OrganizationForm.contextTypes = { diff --git a/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx b/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx index b7ec979a9a..6aafd74b45 100644 --- a/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx +++ b/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx @@ -78,7 +78,7 @@ class OrganizationDetail extends Component { /> - + + {({ me }) => ( + + )} + {error ?
error
: null} ); diff --git a/src/pages/Organizations/screens/OrganizationAdd.jsx b/src/pages/Organizations/screens/OrganizationAdd.jsx index 1b5fd2d355..d5a537a633 100644 --- a/src/pages/Organizations/screens/OrganizationAdd.jsx +++ b/src/pages/Organizations/screens/OrganizationAdd.jsx @@ -11,6 +11,7 @@ import { Tooltip, } from '@patternfly/react-core'; +import { Config } from '../../../contexts/Config'; import { withNetwork } from '../../../contexts/Network'; import CardCloseButton from '../../../components/CardCloseButton'; import OrganizationForm from '../components/OrganizationForm'; @@ -71,10 +72,15 @@ class OrganizationAdd extends React.Component { - + + {({ me }) => ( + + )} + {error ?
error
: ''}
diff --git a/src/util/validators.jsx b/src/util/validators.jsx index 5681242d7d..c3da8d4dbe 100644 --- a/src/util/validators.jsx +++ b/src/util/validators.jsx @@ -21,7 +21,7 @@ export function maxLength (max, i18n) { export function minMaxValue (min, max, i18n) { return value => { - if (typeof value !== 'number' || value > max || value < min) { + if (value < min || value > max) { return i18n._(t`This field must be a number and have a value between ${min} and ${max}`); } return undefined;