From 1dd8175e11059c7527f590464665f9f2fc1a84a0 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 10 Dec 2018 15:00:16 -0500 Subject: [PATCH 1/9] Basic add organization functionality. - Placeholders for Lookup Modal and Ansible Environment dropdown. --- src/api.js | 1 + .../Organizations/views/Organization.add.jsx | 133 ++++++++++++++++-- 2 files changed, 122 insertions(+), 12 deletions(-) diff --git a/src/api.js b/src/api.js index 0976d9f9bc..ddbf367952 100644 --- a/src/api.js +++ b/src/api.js @@ -45,6 +45,7 @@ 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/pages/Organizations/views/Organization.add.jsx b/src/pages/Organizations/views/Organization.add.jsx index 831cc0c243..58581f059b 100644 --- a/src/pages/Organizations/views/Organization.add.jsx +++ b/src/pages/Organizations/views/Organization.add.jsx @@ -3,19 +3,128 @@ import { PageSection, PageSectionVariants, Title, + Form, + FormGroup, + TextInput, + ActionGroup, + Toolbar, + ToolbarGroup, + Button, + Gallery, + Select, + SelectOption, + Card, + CardBody, } from '@patternfly/react-core'; -const { light, medium } = PageSectionVariants; +import { API_ORGANIZATIONS } from '../../../endpoints'; +import api from '../../../api'; +const { light } = PageSectionVariants; -const OrganizationView = () => ( - - - Organization Add - - - This is the add view - - -); +class OrganizationAdd extends React.Component { -export default OrganizationView; + state = { + name: '', + description: '', + instanceGroups: '', + ansible_environment: 'default', + post_endpoint: API_ORGANIZATIONS, + }; + + onSelectChange = (value, _) => { + this.setState({ ansible_environment: value }); + }; + + envs = [ // Placeholder for Ansible Environment Dropdown + { ansible_environment: 'default', label: 'Select Ansible Environment', disabled: true }, + { ansible_environment: '1', label: '1', disabled: false }, + { ansible_environment: '2', label: '2', disabled: false }, + { ansible_environment: '3', label: '3', disabled: false } + ]; + resetForm = () => { + this.setState({ + ...this.state, + name: '', + description: '' + }) + } + handleChange = (_, evt) => { + this.setState({ [evt.target.name]: evt.target.value }); + } + onSubmit = async () => { + const data = Object.assign({}, { ...this.state }); + await api.post(API_ORGANIZATIONS, data); + this.resetForm(); + } + render() { + const { name } = this.state; + const enabled = name.length > 0; // TODO: add better form validation + return ( + + + Organization Add + + + + +
+ + + + + + + + {/* LOOKUP MODAL PLACEHOLDER */} + + + + + + + + + + + + + + + + + +
+
+
+
+
+ ); + } +} + +export default OrganizationAdd; From 9400bad990908d72d30345f242a68e4236dae675 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 10 Dec 2018 11:06:01 -0500 Subject: [PATCH 2/9] Linter fixes. --- src/api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api.js b/src/api.js index ddbf367952..62649fa0e5 100644 --- a/src/api.js +++ b/src/api.js @@ -45,6 +45,7 @@ class APIClient { } get = (endpoint, params = {}) => this.http.get(endpoint, { params }); + post = (endpoint, data) => this.http.post(endpoint, data); } From 27e13ca082d773dfb856fc406d671d3259bae294 Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 10 Dec 2018 15:03:33 -0500 Subject: [PATCH 3/9] Add tests for Organization add view plus code refactoring. --- .../views/Organization.add.test.jsx | 37 +++++++++++++++++++ .../Organizations/views/Organization.add.jsx | 34 ++++++++++------- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/__tests__/pages/Organizations/views/Organization.add.test.jsx b/__tests__/pages/Organizations/views/Organization.add.test.jsx index c8822cfc38..2482c84984 100644 --- a/__tests__/pages/Organizations/views/Organization.add.test.jsx +++ b/__tests__/pages/Organizations/views/Organization.add.test.jsx @@ -11,4 +11,41 @@ describe('', () => { /> ); }); + test('calls "onSelectChange" on dropdown select change', () => { + const spy = jest.spyOn(OrganizationAdd.prototype, 'onSelectChange'); + const wrapper = mount( + + ); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('select').simulate('change'); + expect(spy).toHaveBeenCalled(); + }); + test('calls "handleChange" when input values change', () => { + const spy = jest.spyOn(OrganizationAdd.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.prototype, 'onSubmit'); + const wrapper = mount( + + ); + expect(spy).not.toHaveBeenCalled(); + wrapper.find('button.at-C-SubmitButton').prop('onClick')(); + expect(spy).toBeCalled(); + }); }); diff --git a/src/pages/Organizations/views/Organization.add.jsx b/src/pages/Organizations/views/Organization.add.jsx index 58581f059b..6e3d9b860c 100644 --- a/src/pages/Organizations/views/Organization.add.jsx +++ b/src/pages/Organizations/views/Organization.add.jsx @@ -22,7 +22,14 @@ import api from '../../../api'; const { light } = PageSectionVariants; class OrganizationAdd extends React.Component { + constructor(props) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.onSelectChange = this.onSelectChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.resetForm = this.resetForm.bind(this); + } state = { name: '', description: '', @@ -31,31 +38,30 @@ class OrganizationAdd extends React.Component { post_endpoint: API_ORGANIZATIONS, }; - onSelectChange = (value, _) => { + onSelectChange (value, _) { this.setState({ ansible_environment: value }); }; - - envs = [ // Placeholder for Ansible Environment Dropdown - { ansible_environment: 'default', label: 'Select Ansible Environment', disabled: true }, - { ansible_environment: '1', label: '1', disabled: false }, - { ansible_environment: '2', label: '2', disabled: false }, - { ansible_environment: '3', label: '3', disabled: false } - ]; - resetForm = () => { + resetForm() { this.setState({ ...this.state, name: '', description: '' }) } - handleChange = (_, evt) => { + handleChange(_, evt) { this.setState({ [evt.target.name]: evt.target.value }); } - onSubmit = async () => { + async onSubmit() { const data = Object.assign({}, { ...this.state }); await api.post(API_ORGANIZATIONS, data); this.resetForm(); } + envs = [ // Placeholder for Ansible Environment Dropdown + { ansible_environment: 'default', label: 'Select Ansible Environment', disabled: true }, + { ansible_environment: '1', label: '1', disabled: false }, + { ansible_environment: '2', label: '2', disabled: false }, + { ansible_environment: '3', label: '3', disabled: false } + ]; render() { const { name } = this.state; const enabled = name.length > 0; // TODO: add better form validation @@ -101,9 +107,9 @@ class OrganizationAdd extends React.Component { /> - {this.envs.map((env, index) => ( - + ))} @@ -111,7 +117,7 @@ class OrganizationAdd extends React.Component { - + From d047bc876af3cb335e8ba89fbf9cb6687be332eb Mon Sep 17 00:00:00 2001 From: kialam Date: Mon, 10 Dec 2018 20:41:47 -0500 Subject: [PATCH 4/9] Basic Ansible Environment Select Component - Component conditionally renders based on # of virtual environments. - User can add an Organization and associate it with a virtual environment. --- .../AnsibleEnvironmentSelect.test.jsx | 18 +++++++ .../views/Organization.add.test.jsx | 12 ----- .../AnsibleEnvironmentSelect.jsx | 53 +++++++++++++++++++ .../AnsibleEnvironmentSelect/index.js | 3 ++ .../Organizations/views/Organization.add.jsx | 28 ++++------ 5 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 __tests__/components/AnsibleEnvironmentSelect.test.jsx create mode 100644 src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx create mode 100644 src/components/AnsibleEnvironmentSelect/index.js diff --git a/__tests__/components/AnsibleEnvironmentSelect.test.jsx b/__tests__/components/AnsibleEnvironmentSelect.test.jsx new file mode 100644 index 0000000000..312b033547 --- /dev/null +++ b/__tests__/components/AnsibleEnvironmentSelect.test.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import AnsibleEnvironmentSelect from '../../src/components/AnsibleEnvironmentSelect'; + +describe('', () => { + test('initially renders succesfully', async() => { + const wrapper = mount( {}} />); + wrapper.setState({ isHidden: false }); + }); + test('calls "onSelectChange" on dropdown select change', () => { + const spy = jest.spyOn(AnsibleEnvironmentSelect.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 2482c84984..434621985d 100644 --- a/__tests__/pages/Organizations/views/Organization.add.test.jsx +++ b/__tests__/pages/Organizations/views/Organization.add.test.jsx @@ -11,18 +11,6 @@ describe('', () => { /> ); }); - test('calls "onSelectChange" on dropdown select change', () => { - const spy = jest.spyOn(OrganizationAdd.prototype, 'onSelectChange'); - const wrapper = mount( - - ); - expect(spy).not.toHaveBeenCalled(); - wrapper.find('select').simulate('change'); - expect(spy).toHaveBeenCalled(); - }); test('calls "handleChange" when input values change', () => { const spy = jest.spyOn(OrganizationAdd.prototype, 'handleChange'); const wrapper = mount( diff --git a/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx b/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx new file mode 100644 index 0000000000..d07bfdaac6 --- /dev/null +++ b/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + FormGroup, + Select, + SelectOption, +} from '@patternfly/react-core'; +import { API_CONFIG } from '../../endpoints'; +import api from '../../api'; + +class AnsibleEnvironmentSelect extends React.Component { + constructor(props) { + super(props); + this.onSelectChange = this.onSelectChange.bind(this); + } + + state = { + custom_virtualenvs: [], + isHidden: true, + } + + async componentDidMount() { + 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({ isHidden: !this.state.isHidden }); + } + } + + onSelectChange(val, _) { + this.props.selectChange(val); + } + + render() { + const { isHidden } = this.state; + if (isHidden) { + return null; + } else { + return ( + + + + ); + } + } +} + +export default AnsibleEnvironmentSelect; diff --git a/src/components/AnsibleEnvironmentSelect/index.js b/src/components/AnsibleEnvironmentSelect/index.js new file mode 100644 index 0000000000..2e7a7f44a3 --- /dev/null +++ b/src/components/AnsibleEnvironmentSelect/index.js @@ -0,0 +1,3 @@ +import AnsibleEnvironmentSelect from './AnsibleEnvironmentSelect'; + +export default AnsibleEnvironmentSelect; diff --git a/src/pages/Organizations/views/Organization.add.jsx b/src/pages/Organizations/views/Organization.add.jsx index 6e3d9b860c..0c8fc6c57d 100644 --- a/src/pages/Organizations/views/Organization.add.jsx +++ b/src/pages/Organizations/views/Organization.add.jsx @@ -11,14 +11,13 @@ import { ToolbarGroup, Button, Gallery, - Select, - SelectOption, Card, CardBody, } from '@patternfly/react-core'; import { API_ORGANIZATIONS } from '../../../endpoints'; import api from '../../../api'; +import AnsibleEnvironmentSelect from '../../../components/AnsibleEnvironmentSelect' const { light } = PageSectionVariants; class OrganizationAdd extends React.Component { @@ -30,17 +29,19 @@ class OrganizationAdd extends React.Component { this.onSubmit = this.onSubmit.bind(this); this.resetForm = this.resetForm.bind(this); } + state = { name: '', description: '', instanceGroups: '', - ansible_environment: 'default', + custom_virtualenv: '', post_endpoint: API_ORGANIZATIONS, }; - onSelectChange (value, _) { - this.setState({ ansible_environment: value }); + onSelectChange(value, _) { + this.setState({ custom_virtualenv: value }); }; + resetForm() { this.setState({ ...this.state, @@ -48,20 +49,17 @@ class OrganizationAdd extends React.Component { 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(); } - envs = [ // Placeholder for Ansible Environment Dropdown - { ansible_environment: 'default', label: 'Select Ansible Environment', disabled: true }, - { ansible_environment: '1', label: '1', disabled: false }, - { ansible_environment: '2', label: '2', disabled: false }, - { ansible_environment: '3', label: '3', disabled: false } - ]; + render() { const { name } = this.state; const enabled = name.length > 0; // TODO: add better form validation @@ -106,13 +104,7 @@ class OrganizationAdd extends React.Component { onChange={this.handleChange} /> - - - + From 9c7d449a4d334782e2f19369b85c1a9cfa95437c Mon Sep 17 00:00:00 2001 From: kialam Date: Tue, 11 Dec 2018 16:20:55 -0500 Subject: [PATCH 5/9] Abstract out API get request to Add Org component. - This makes it so we now have a generic select dropdown where we can pass data down as props. --- .../AnsibleEnvironmentSelect.test.jsx | 11 +++---- .../AnsibleEnvironmentSelect.jsx | 29 ++++--------------- .../AnsibleEnvironmentSelect/index.js | 4 +-- .../Organizations/views/Organization.add.jsx | 23 ++++++++++++--- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/__tests__/components/AnsibleEnvironmentSelect.test.jsx b/__tests__/components/AnsibleEnvironmentSelect.test.jsx index 312b033547..0bcf9d3ded 100644 --- a/__tests__/components/AnsibleEnvironmentSelect.test.jsx +++ b/__tests__/components/AnsibleEnvironmentSelect.test.jsx @@ -1,15 +1,16 @@ import React from 'react'; import { mount } from 'enzyme'; -import AnsibleEnvironmentSelect from '../../src/components/AnsibleEnvironmentSelect'; +import AnsibleSelect from '../../src/components/AnsibleEnvironmentSelect'; -describe('', () => { +const mockData = ['foo', 'bar']; +describe('', () => { test('initially renders succesfully', async() => { - const wrapper = mount( {}} />); + const wrapper = mount( {}} />); wrapper.setState({ isHidden: false }); }); test('calls "onSelectChange" on dropdown select change', () => { - const spy = jest.spyOn(AnsibleEnvironmentSelect.prototype, 'onSelectChange'); - const wrapper = mount( {}} />); + const spy = jest.spyOn(AnsibleSelect.prototype, 'onSelectChange'); + const wrapper = mount( {}} />); wrapper.setState({ isHidden: false }); expect(spy).not.toHaveBeenCalled(); wrapper.find('select').simulate('change'); diff --git a/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx b/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx index d07bfdaac6..632a5b544f 100644 --- a/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx +++ b/src/components/AnsibleEnvironmentSelect/AnsibleEnvironmentSelect.jsx @@ -4,43 +4,26 @@ import { Select, SelectOption, } from '@patternfly/react-core'; -import { API_CONFIG } from '../../endpoints'; -import api from '../../api'; -class AnsibleEnvironmentSelect extends React.Component { +class AnsibleSelect extends React.Component { constructor(props) { super(props); this.onSelectChange = this.onSelectChange.bind(this); } - state = { - custom_virtualenvs: [], - isHidden: true, - } - - async componentDidMount() { - 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({ isHidden: !this.state.isHidden }); - } - } - onSelectChange(val, _) { this.props.selectChange(val); } render() { - const { isHidden } = this.state; - if (isHidden) { + const hide = this.props.hidden; + if (hide) { return null; } else { return ( - + @@ -50,4 +33,4 @@ class AnsibleEnvironmentSelect extends React.Component { } } -export default AnsibleEnvironmentSelect; +export default AnsibleSelect; diff --git a/src/components/AnsibleEnvironmentSelect/index.js b/src/components/AnsibleEnvironmentSelect/index.js index 2e7a7f44a3..1f2327e390 100644 --- a/src/components/AnsibleEnvironmentSelect/index.js +++ b/src/components/AnsibleEnvironmentSelect/index.js @@ -1,3 +1,3 @@ -import AnsibleEnvironmentSelect from './AnsibleEnvironmentSelect'; +import AnsibleSelect from './AnsibleEnvironmentSelect'; -export default AnsibleEnvironmentSelect; +export default AnsibleSelect; diff --git a/src/pages/Organizations/views/Organization.add.jsx b/src/pages/Organizations/views/Organization.add.jsx index 0c8fc6c57d..3e03511a77 100644 --- a/src/pages/Organizations/views/Organization.add.jsx +++ b/src/pages/Organizations/views/Organization.add.jsx @@ -15,9 +15,9 @@ import { CardBody, } from '@patternfly/react-core'; -import { API_ORGANIZATIONS } from '../../../endpoints'; +import { API_ORGANIZATIONS, API_CONFIG } from '../../../endpoints'; import api from '../../../api'; -import AnsibleEnvironmentSelect from '../../../components/AnsibleEnvironmentSelect' +import AnsibleSelect from '../../../components/AnsibleEnvironmentSelect' const { light } = PageSectionVariants; class OrganizationAdd extends React.Component { @@ -35,7 +35,8 @@ class OrganizationAdd extends React.Component { description: '', instanceGroups: '', custom_virtualenv: '', - post_endpoint: API_ORGANIZATIONS, + custom_virtualenvs: [], + hideAnsibleSelect: true, }; onSelectChange(value, _) { @@ -60,6 +61,14 @@ class OrganizationAdd extends React.Component { this.resetForm(); } + async componentDidMount() { + 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 }); + } + } render() { const { name } = this.state; const enabled = name.length > 0; // TODO: add better form validation @@ -104,7 +113,13 @@ class OrganizationAdd extends React.Component { onChange={this.handleChange} /> - + - + @@ -143,4 +155,4 @@ class OrganizationAdd extends React.Component { } } -export default OrganizationAdd; +export default withRouter(OrganizationAdd);