diff --git a/__tests__/components/AnsibleSelect.test.jsx b/__tests__/components/AnsibleSelect.test.jsx new file mode 100644 index 0000000000..47a068ef4f --- /dev/null +++ b/__tests__/components/AnsibleSelect.test.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import AnsibleSelect from '../../src/components/AnsibleSelect'; + +const mockData = ['foo', 'bar']; +describe('', () => { + test('initially renders succesfully', async() => { + const wrapper = mount( {}} />); + wrapper.setState({ isHidden: false }); + }); + test('calls "onSelectChange" on dropdown select change', () => { + const spy = jest.spyOn(AnsibleSelect.prototype, 'onSelectChange'); + const wrapper = mount( {}} />); + wrapper.setState({ isHidden: false }); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('select').simulate('change'); + expect(spy).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/__tests__/pages/Organizations/views/Organization.add.test.jsx b/__tests__/pages/Organizations/views/Organization.add.test.jsx index c8822cfc38..8c25eb1eef 100644 --- a/__tests__/pages/Organizations/views/Organization.add.test.jsx +++ b/__tests__/pages/Organizations/views/Organization.add.test.jsx @@ -1,14 +1,60 @@ import React from 'react'; import { mount } from 'enzyme'; import OrganizationAdd from '../../../../src/pages/Organizations/views/Organization.add'; +import { MemoryRouter } from 'react-router-dom'; describe('', () => { test('initially renders succesfully', () => { mount( - + + + ); }); + test('calls "handleChange" when input values change', () => { + const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'handleChange'); + const wrapper = mount( + + + + ); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } }); + wrapper.find('input#add-org-form-description').simulate('change', { target: { value: 'bar' } }); + expect(spy).toHaveBeenCalledTimes(2); + }); + test('calls "onSubmit" when Save button is clicked', () => { + const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit'); + const wrapper = mount( + + + + ); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('button.at-C-SubmitButton').prop('onClick')(); + expect(spy).toBeCalled(); + }); + test('calls "onCancel" when Cancel button is clicked', () => { + const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel'); + const wrapper = mount( + + + + ); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('button.at-C-CancelButton').prop('onClick')(); + expect(spy).toBeCalled(); + }); }); diff --git a/src/api.js b/src/api.js index 0976d9f9bc..62649fa0e5 100644 --- a/src/api.js +++ b/src/api.js @@ -45,6 +45,8 @@ class APIClient { } get = (endpoint, params = {}) => this.http.get(endpoint, { params }); + + post = (endpoint, data) => this.http.post(endpoint, data); } export default new APIClient(); diff --git a/src/app.scss b/src/app.scss index 5f79cc5505..2d3943a597 100644 --- a/src/app.scss +++ b/src/app.scss @@ -118,3 +118,13 @@ --pf-c-about-modal-box--MaxHeight: 40rem; --pf-c-about-modal-box--MaxWidth: 63rem; } + + +// +// layout styles +// +.at-align-right { + display: flex; + flex-direction: row; + justify-content: flex-end; +} \ No newline at end of file diff --git a/src/components/AnsibleSelect/AnsibleSelect.jsx b/src/components/AnsibleSelect/AnsibleSelect.jsx new file mode 100644 index 0000000000..d411c1d17a --- /dev/null +++ b/src/components/AnsibleSelect/AnsibleSelect.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + FormGroup, + Select, + SelectOption, +} from '@patternfly/react-core'; + +class AnsibleSelect extends React.Component { + constructor(props) { + super(props); + this.onSelectChange = this.onSelectChange.bind(this); + } + + onSelectChange(val, _) { + this.props.selectChange(val); + } + + render() { + const { hidden } = this.props; + if (hidden) { + return null; + } else { + return ( + + + + ); + } + } +} + +export default AnsibleSelect; diff --git a/src/components/AnsibleSelect/index.js b/src/components/AnsibleSelect/index.js new file mode 100644 index 0000000000..219abcfefd --- /dev/null +++ b/src/components/AnsibleSelect/index.js @@ -0,0 +1,3 @@ +import AnsibleSelect from './AnsibleSelect'; + +export default AnsibleSelect; diff --git a/src/pages/Organizations/views/Organization.add.jsx b/src/pages/Organizations/views/Organization.add.jsx index a1218d3465..023817fe1d 100644 --- a/src/pages/Organizations/views/Organization.add.jsx +++ b/src/pages/Organizations/views/Organization.add.jsx @@ -1,24 +1,158 @@ import React, { Fragment } from 'react'; +import { withRouter } from 'react-router-dom'; import { Trans } from '@lingui/macro'; import { PageSection, PageSectionVariants, Title, + Form, + FormGroup, + TextInput, + ActionGroup, + Toolbar, + ToolbarGroup, + Button, + Gallery, + Card, + CardBody, } from '@patternfly/react-core'; -const { light, medium } = PageSectionVariants; +import { API_ORGANIZATIONS, API_CONFIG } from '../../../endpoints'; +import api from '../../../api'; +import AnsibleSelect from '../../../components/AnsibleSelect' +const { light } = PageSectionVariants; -const OrganizationView = () => ( - - - - <Trans>Organization Add</Trans> - - - - This is the add view - - -); +class OrganizationAdd extends React.Component { + constructor(props) { + super(props); -export default OrganizationView; + this.handleChange = this.handleChange.bind(this); + this.onSelectChange = this.onSelectChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.resetForm = this.resetForm.bind(this); + this.onCancel = this.onCancel.bind(this); + } + + state = { + name: '', + description: '', + instanceGroups: '', + custom_virtualenv: '', + custom_virtualenvs: [], + hideAnsibleSelect: true, + error:'', + }; + + onSelectChange(value, _) { + this.setState({ custom_virtualenv: value }); + }; + + resetForm() { + this.setState({ + ...this.state, + name: '', + description: '' + }) + } + + handleChange(_, evt) { + this.setState({ [evt.target.name]: evt.target.value }); + } + + async onSubmit() { + const data = Object.assign({}, { ...this.state }); + await api.post(API_ORGANIZATIONS, data); + this.resetForm(); + } + + onCancel() { + this.props.history.push('/organizations'); + } + + async componentDidMount() { + try { + const { data } = await api.get(API_CONFIG); + this.setState({ custom_virtualenvs: [...data.custom_virtualenvs] }); + if (this.state.custom_virtualenvs.length > 1) { + // Show dropdown if we have more than one ansible environment + this.setState({ hideAnsibleSelect: !this.state.hideAnsibleSelect }); + } + } catch (error) { + this.setState({ error }) + } + + } + render() { + const { name } = this.state; + const enabled = name.length > 0; // TODO: add better form validation + return ( + + + + <Trans>Organization Add</Trans> + + + + + +
+ + + + + + + + {/* LOOKUP MODAL PLACEHOLDER */} + + + + + + + + + + + + + + +
+
+
+
+
+ ); + } +} + +export default withRouter(OrganizationAdd);