-
Notifications
You must be signed in to change notification settings - Fork 42
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
Conversation
42200e9
to
c192851
Compare
0f1ee50
to
eb2a14b
Compare
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 |
There was a problem hiding this comment.
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 require
d, well actually autoloaded).
There was a problem hiding this comment.
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.
eb2a14b
to
111b4e3
Compare
@martinpovolny @Hyperkid123 I organized files in 3 directories to make it more readable and reusable:
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? |
There was a problem hiding this 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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
app/javascript/utils/api.js
Outdated
resource: {...values, router_ref: routerRef}, | ||
}).then(response => { | ||
let res = response['results'][0]; | ||
window.add_flash(res.message, res.success ? 'success' : 'error'); |
There was a problem hiding this comment.
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...)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
111b4e3
to
7a1b6c5
Compare
There was a problem hiding this 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, |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
app/javascript/utils/api.js
Outdated
resource: {...values, router_ref: routerRef}, | ||
}).then(response => { | ||
let res = response['results'][0]; | ||
window.add_flash(res.message, res.success ? 'success' : 'error'); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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); } |
There was a problem hiding this comment.
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 ;
@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. |
7e86dca
to
7624292
Compare
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? |
Trying to find a word for such PRs. They are not "followup PRs" because we need to merge them upfront. Perhaps "followme PRs"? |
@miha-plesko i think merging should be done by someone in your team. But i have no problem doing reviews. |
@Hyperkid123 if you approve I'll merge this |
7624292
to
2e5a864
Compare
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
app/javascript/utils/validators.js
Outdated
import { addValidator } from 'redux-form-validators'; | ||
|
||
export const ip4Validator = addValidator({ | ||
defaultMessage: 'Must be IPv4 address', |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 />); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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} />); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here are two it
s: 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, done.
spec/javascript/setup.js
Outdated
|
||
// Global variables that Components usually get from elsewhere. | ||
global.RECORD_ID = 123; | ||
global.ManageIQ = { record: { recordId: global.RECORD_ID } }; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
2e5a864
to
359868a
Compare
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
app/javascript/utils/validators.js
Outdated
import { addValidator } from 'redux-form-validators'; | ||
|
||
export const ip4Validator = addValidator({ | ||
defaultMessage: 'Must be IPv4 address', |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 />); |
There was a problem hiding this comment.
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} />); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here are two it
s: 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.
spec/javascript/setup.js
Outdated
|
||
// Global variables that Components usually get from elsewhere. | ||
global.RECORD_ID = 123; | ||
global.ManageIQ = { record: { recordId: global.RECORD_ID } }; |
There was a problem hiding this comment.
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]>
359868a
to
056dfef
Compare
Just pushed updated version with render auxilary functions defined in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good
Checked commit miha-plesko@056dfef with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0 |
@agrare we have thumbsup from @Hyperkid123, hit the button at will. |
React component for creating Nuage subnet
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.
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