Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React component for creating Nuage subnet #118

Merged
merged 1 commit into from
Aug 13, 2018

Conversation

miha-plesko
Copy link
Contributor

@miha-plesko miha-plesko commented Jul 27, 2018

With this commit we implement React component used to capture user inputs for creating CloudSubnet. Also, we add button to NetworkRouter details page to trigger the flow.

image

image

Video: http://x.k00.fr/subnetcreate
RFE BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1574972

Related PRs:

(following ManageIQ/manageiq-providers-amazon#469)

/cc @Ladas @agrare

super(props);
this.handleFormStateUpdate = this.handleFormStateUpdate.bind(this);
this.routerRef = 'ebca9d3b-53a4-4fd4-8268-196cb3fa6072'; // TODO: pass from Rails
this.emsId = 4; // TODO: pass from Rails
Copy link
Member

@martinpovolny martinpovolny Aug 3, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally you have ManageIQ.record.recordId with the id of the entity being displayed. If you need some other params, then the best way is to fetch them via API starting with the recordId.

Passing arguments through buttons from the toolbar will not help. Those are stored in a singleton evaluated at the first use of a toolbar (when the toolbar is required, well actually autoloaded).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will apply Monday morning. We had a discussion with @Hyperkid123 and came to the same conclusion 👍 . BTW UI team did such a great job supporting React Components that I'm preparing a blogpost of it. Thank you so much for this.

@miha-plesko
Copy link
Contributor Author

@martinpovolny @Hyperkid123 I organized files in 3 directories to make it more readable and reusable:

manageiq-providers-nuage/app/javascript/
├── components
│   └── create-nuage-cloud-subnet-form.jsx
├── forms
│   └── nuage-cloud-subnet-form.jsx
└── utils
    ├── api.js
    └── validators.js

Also, I was finally able to remove all the hard-coded stuff and obtain data from API hence I'm removing WIP tag on the PR. Kindly ask for your opinion, can we push it forward?

@miha-plesko miha-plesko removed the wip label Aug 6, 2018
@miha-plesko miha-plesko changed the title [WIP] React component for creating Nuage subnet React component for creating Nuage subnet Aug 6, 2018
Copy link

@Hyperkid123 Hyperkid123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS looks good.

The folder structure is for you to decide. Maybe i would add forms to under the components folder (because its a component also). But you should discuss this probably with your team.

this.handleFormStateUpdate = this.handleFormStateUpdate.bind(this);
this.state = {
emsId: -1,
routerRef: -1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i would prefer some bool value instead if this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

<Field
name="name"
component={FinalFormField}
label="Name"
Copy link

@Hyperkid123 Hyperkid123 Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use i18n for all strings. name={__('Name')} should work. __ (two underscores) should be available on window.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

resource: {...values, router_ref: routerRef},
}).then(response => {
let res = response['results'][0];
window.add_flash(res.message, res.success ? 'success' : 'error');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is safer to cycle through all result messages so you don't miss anything. response.forEach(item => window.add_flash...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

Copy link
Contributor Author

@miha-plesko miha-plesko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for review @Hyperkid123, all what you suggest makes sense. I've applied and repushed. Do we also need to provide some kind of unit tests for it?

this.handleFormStateUpdate = this.handleFormStateUpdate.bind(this);
this.state = {
emsId: -1,
routerRef: -1,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

<Field
name="name"
component={FinalFormField}
label="Name"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

resource: {...values, router_ref: routerRef},
}).then(response => {
let res = response['results'][0];
window.add_flash(res.message, res.success ? 'success' : 'error');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

super(props);
this.handleFormStateUpdate = this.handleFormStateUpdate.bind(this);
this.state = {
emsId: false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok now i don't like it either :D Now when i see it i would expect that those values will be bool in the future which is not true. I think it would be best just keep them undefined until you initialize them.

payload: {
newRecord: true,
pristine: true,
addClicked: () => { createSubnet(this.state.values, this.state.emsId, this.state.routerRef); }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the curly brackets around createSubnet(...) function. You need them only for multi-line function body. Also you can drop the ;

@Hyperkid123
Copy link

@miha-plesko yeah right i've completely forgot about tests. You will need to add additional config to enable testing.

I would recommend using JEST for JS testing and enzyme for React specific tests. You can check our config in react-ui-components (or in manageiq-ui-classic, you can pick), but they have got pretty good docs. And i can help you if you hit a wall or something.

@miha-plesko miha-plesko force-pushed the create-subnet-ui branch 2 times, most recently from 7e86dca to 7624292 Compare August 7, 2018 08:44
@miha-plesko
Copy link
Contributor Author

Okay so this PR has been split into three separate PRs:

The 1st and 2nd PRs have already been reviewed so I think we should merge them at this point so we can work further. I suggest either @Ladas or @agrare hits the button, should they agree, or @Hyperkid123 if you can suggest some guy that is more UI-related and has merge permissions, that's even better?

@miha-plesko
Copy link
Contributor Author

Trying to find a word for such PRs. They are not "followup PRs" because we need to merge them upfront. Perhaps "followme PRs"?

@Hyperkid123
Copy link

Hyperkid123 commented Aug 7, 2018

@miha-plesko i think merging should be done by someone in your team. But i have no problem doing reviews.

@agrare
Copy link
Member

agrare commented Aug 7, 2018

@Hyperkid123 if you approve I'll merge this

@Hyperkid123
Copy link

@agrare ok i would suggest merge #121 and then #122 and this PR after that.

@miha-plesko
Copy link
Contributor Author

Okay @Hyperkid123 I came up with jest/enzyme tests. Took me a while to figure how to handle promises but I think it eventually came out quite readable. Thanks to unit tests it turned out we didn't handle API error anywhere. Added it now.

Looking forward to your opinion, especially on unit tests!

import { FinalFormField, FinalFormTextArea, FinalFormSelect } from '@manageiq/react-ui-components/dist/forms';
import { ip4Validator, netmaskValidator } from '../../utils/validators'

class NuageCloudSubnetForm extends Component {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you are writing stateless components (without state or any lifecycle methods) it is generally better to write those components as functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah my thinking was to have it all done the same way, but I agree, we can use function here to spare some LOC.

import { addValidator } from 'redux-form-validators';

export const ip4Validator = addValidator({
defaultMessage: 'Must be IPv4 address',
Copy link

@Hyperkid123 Hyperkid123 Aug 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thin you should use i18n here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh

});
});

const renderComponent = () => shallowRedux(<CreateNuageCloudSubnetForm />);
Copy link

@Hyperkid123 Hyperkid123 Aug 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think u should use beforeEach (or beforeAll) functions that are available in jest. (see https://jestjs.io/docs/en/setup-teardown)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um, but then I would always render the same component in same context, right? Here I want to render component in different context: once when fetchRouter succeeds, once when it fails etc. As far as I've been looking, it's not possible in it to influence the beforeEach in any way so I think I cannot let it know what context I want. Or did I miss something?

});
});

const renderComponent = (loading = false) => shallowRedux(<NuageCloudSubnetForm updateFormState={jest.fn()} loading={loading} />);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here use the beforeEach to prepare common data or components for tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here are two its: first one renders component with loading=true and the other one with loading=false. If I do the rendering in beforeEach then it will render it in one way always, right?

Feels like I'm missing something, kindly ask for hint.

let component = renderComponent();
return fetchRouterMock().then(() => {
component.update();
expect(fetchRouterMock).toHaveBeenCalledWith(RECORD_ID);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not against globals for testing, but i don't think its a good idea to store test specific in them values. It should be immediately clear what is inside such values. I think this value should be set in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, done.


// Global variables that Components usually get from elsewhere.
global.RECORD_ID = 123;
global.ManageIQ = { record: { recordId: global.RECORD_ID } };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never used in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following your suggestion I now set this in beforeEach block in the test itself. But I find it more readable to do it like this:

// setup.js
global.ManageIQ = { record: { recordId: -1 } };
// create-nuage-cloud-subnet-form.js
beforeEach(() => {
  ManageIQ.record.recordId = 123;
});

than to do it like this:

// create-nuage-cloud-subnet-form.js
beforeEach(() => {
  global.ManageIQ = { record: { recordId: 123 } };
});

Please let me know if you disagree.

Copy link
Contributor Author

@miha-plesko miha-plesko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for review @Hyperkid123 . I'm not sure if we can use beforeEach in all cases, please see below.

import { FinalFormField, FinalFormTextArea, FinalFormSelect } from '@manageiq/react-ui-components/dist/forms';
import { ip4Validator, netmaskValidator } from '../../utils/validators'

class NuageCloudSubnetForm extends Component {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah my thinking was to have it all done the same way, but I agree, we can use function here to spare some LOC.

import { addValidator } from 'redux-form-validators';

export const ip4Validator = addValidator({
defaultMessage: 'Must be IPv4 address',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh

let component = renderComponent();
return fetchRouterMock().then(() => {
component.update();
expect(fetchRouterMock).toHaveBeenCalledWith(RECORD_ID);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, done.

});
});

const renderComponent = () => shallowRedux(<CreateNuageCloudSubnetForm />);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um, but then I would always render the same component in same context, right? Here I want to render component in different context: once when fetchRouter succeeds, once when it fails etc. As far as I've been looking, it's not possible in it to influence the beforeEach in any way so I think I cannot let it know what context I want. Or did I miss something?

});
});

const renderComponent = (loading = false) => shallowRedux(<NuageCloudSubnetForm updateFormState={jest.fn()} loading={loading} />);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here are two its: first one renders component with loading=true and the other one with loading=false. If I do the rendering in beforeEach then it will render it in one way always, right?

Feels like I'm missing something, kindly ask for hint.


// Global variables that Components usually get from elsewhere.
global.RECORD_ID = 123;
global.ManageIQ = { record: { recordId: global.RECORD_ID } };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following your suggestion I now set this in beforeEach block in the test itself. But I find it more readable to do it like this:

// setup.js
global.ManageIQ = { record: { recordId: -1 } };
// create-nuage-cloud-subnet-form.js
beforeEach(() => {
  ManageIQ.record.recordId = 123;
});

than to do it like this:

// create-nuage-cloud-subnet-form.js
beforeEach(() => {
  global.ManageIQ = { record: { recordId: 123 } };
});

Please let me know if you disagree.

With this commit we implement react component used to capture
user inputs for creating CloudSubnet. Also, we add button to
NetworkRouter details page to trigger the flow.

RFE BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1574972

Signed-off-by: Miha Pleško <[email protected]>
@miha-plesko
Copy link
Contributor Author

Just pushed updated version with render auxilary functions defined in beforeAll blocks instead on the bottom of files.

Copy link

@Hyperkid123 Hyperkid123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@miq-bot
Copy link
Member

miq-bot commented Aug 13, 2018

Checked commit miha-plesko@056dfef with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
1 file checked, 0 offenses detected
Everything looks fine. ⭐

@miha-plesko
Copy link
Contributor Author

@agrare we have thumbsup from @Hyperkid123, hit the button at will.

@agrare agrare merged commit 056dfef into ManageIQ:master Aug 13, 2018
agrare added a commit that referenced this pull request Aug 13, 2018
React component for creating Nuage subnet
@agrare agrare added this to the Sprint 92 Ending Aug 13, 2018 milestone Aug 13, 2018
@miha-plesko miha-plesko deleted the create-subnet-ui branch August 31, 2018 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants