-
Notifications
You must be signed in to change notification settings - Fork 994
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
Fixes #20469 - Register react components from plugins #4733
Conversation
@xprazak2, the Redmine ticket used is for a different project than the one associated with this GitHub repository. Please either:
If changing the ticket number used, remember to update the PR title and the commit message (using This message was auto-generated by Foreman's prprocessor |
Issues: #20429 |
throw `Component not found: ${name} among ${registeredComponents()}` | ||
} | ||
let ComponentName = currentComponent.type; | ||
return <ComponentName data={ currentComponent.data ? data : undefined } store={ currentComponent.store ? store : undefined } />; |
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.
Line 66 exceeds the maximum line length of 100 max-len
if (!currentComponent) { | ||
throw `Component not found: ${name} among ${registeredComponents()}` | ||
} | ||
let ComponentName = currentComponent.type; |
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.
Expected blank line after variable declarations newline-after-var
let markup = (name, data, store) => { | ||
let currentComponent = getComponent(name); | ||
if (!currentComponent) { | ||
throw `Component not found: ${name} among ${registeredComponents()}` |
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.
Expected an object to be thrown no-throw-literal
Missing semicolon semi
}; | ||
|
||
let markup = (name, data, store) => { | ||
let currentComponent = getComponent(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.
Expected blank line after variable declarations newline-after-var
} | ||
|
||
registry[name] = component; | ||
console.log('Component registered with name ' + 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.
Unexpected console statement no-console
|
||
let register = (name, component) => { | ||
if (registry[name]) { | ||
throw `Component name already taken: ${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.
Expected an object to be thrown no-throw-literal
import PowerStatus from '../components/hosts/powerStatus/'; | ||
import NotificationContainer from '../components/notifications/'; | ||
import ToastsList from '../components/toastNotifications/'; | ||
import StorageContainer from '../components/hosts/storage/vmware/'; |
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.
'StorageContainer' is defined but never used no-unused-vars
@@ -0,0 +1,72 @@ | |||
import React from 'react'; | |||
import store from '../redux'; |
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.
'store' is defined but never used no-unused-vars
@xprazak2, the Redmine ticket used is for a different project than the one associated with this GitHub repository. Please either:
If changing the ticket number used, remember to update the PR title and the commit message (using This message was auto-generated by Foreman's prprocessor |
550073d
to
a372615
Compare
Looking good, I hope to try it soon. |
a372615
to
b3988c4
Compare
@@ -0,0 +1,85 @@ | |||
import React from 'react'; | |||
import store from '../redux'; |
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.
'store' is defined but never used no-unused-vars
b3988c4
to
493a67c
Compare
config/webpack.config.js
Outdated
foremanReactService: | ||
path.join(__dirname, | ||
'../webpack/assets/javascripts/services'), | ||
foremanNodeModules: |
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 here because of packaging-related changes. Plugins need to find foreman's node_modules somehow.
import React from 'foremanNodeModules/react';
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.
does it mean that all plugins need to prefix their requirements to foreman node module? that would explain why in my tests i had a very large vendor file and a very large plugin specific file...I could see this being fairly annoying + hard to use npm packages that include common other libs (e.g. a react-date picker or something like that)
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 agree it is a nuisance, but import React from 'react';
in plugins does not work and I haven't found a better solution yet. I am not sure what you mean about packages that include common other libs, could you explain please?
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.
for example, if I import a 3rd party react component (lets say a date picker) then it code will include
import React from 'react';
This will:
a. make us pull another copy of react into the plugin node modules
b. will include another copy of react in the plugin bundle.
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.
/cc @dLobatog - this is probably a side effect we did not expect.
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 like a great start, I've left a few comments inline.
Other things to consider for this pr:
- tests
- ability for a plugin to add customer actions/reducers.
- reuse the same eslint configuration as core by default.
does a factory (such as described at https://medium.com/@SntsDev/the-factory-pattern-in-js-es6-78f0afad17e9) make any sense in this context?
thanks @xprazak2 !
app/views/layouts/base.html.erb
Outdated
@@ -17,6 +17,9 @@ | |||
<%= csrf_meta_tags %> | |||
<%= javascript_include_tag *webpack_asset_paths('vendor', :extension => 'js'), "data-turbolinks-track" => true %> | |||
<%= javascript_include_tag *webpack_asset_paths('bundle', :extension => 'js'), "data-turbolinks-track" => true %> | |||
<% webpacked_plugins_js_tags.each do |item| %> | |||
<%= javascript_include_tag *item, "data-turbolinks-track" => true %> |
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.
should this be optional? e.g. maybe not load all js on all pages?
config/webpack.config.js
Outdated
foremanReactService: | ||
path.join(__dirname, | ||
'../webpack/assets/javascripts/services'), | ||
foremanNodeModules: |
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.
does it mean that all plugins need to prefix their requirements to foreman node module? that would explain why in my tests i had a very large vendor file and a very large plugin specific file...I could see this being fairly annoying + hard to use npm packages that include common other libs (e.g. a react-date picker or something like that)
let registry = { | ||
PieChart: { | ||
type: PieChart, | ||
store: true, |
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.
can store and data default to true (so you dry)?
} | ||
}; | ||
|
||
let register = (name, 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.
why let vs const?
let currentComponent = getComponent(name); | ||
|
||
if (!currentComponent) { | ||
throw new Error(`Component not found: ${name} among ${registeredComponents()}`); |
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.
nitpick: extra whitespace between found: and ${name}
493a67c
to
3cb8db2
Compare
const currentComponent = this.getComponent(name); | ||
|
||
if (!currentComponent) { | ||
throw new Error(`Component not found: ${name} among ${registeredComponents()}`); |
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.
'registeredComponents' is not defined no-undef
return splitDirs.length > 2 ? splitDirs[1] : pluginDirs; | ||
}; | ||
|
||
var webpackDirs = execSync(path.join(__dirname, './plugin_webpack_directories.rb'), { |
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.
Unexpected var, use let or const instead no-var
script/plugin_webpack_directories.js
Outdated
// If we get multiple lines, then the plugin_webpack_directories.rb script | ||
// has on the stdout more that just the JSON we want, so we use newline to split and check. | ||
var sanitizeWebpackDirs = function (pluginDirs) { | ||
var splitDirs = pluginDirs.toString().split("\n").reverse(); |
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.
Unexpected var, use let or const instead no-var
Strings must use singlequote quotes
|
||
// If we get multiple lines, then the plugin_webpack_directories.rb script | ||
// has on the stdout more that just the JSON we want, so we use newline to split and check. | ||
var sanitizeWebpackDirs = function (pluginDirs) { |
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.
Unexpected var, use let or const instead no-var
script/plugin_webpack_directories.js
Outdated
'use strict'; | ||
|
||
var execSync = require('child_process').execSync; | ||
var path = require('path'); |
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.
Unexpected var, use let or const instead no-var
script/plugin_webpack_directories.js
Outdated
@@ -0,0 +1,18 @@ | |||
'use strict'; | |||
|
|||
var execSync = require('child_process').execSync; |
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.
Unexpected var, use let or const instead no-var
app/helpers/reactjs_helper.rb
Outdated
|
||
bundle_names.map do |name| | ||
javascript_include_tag *webpack_asset_paths(name, :extension => 'js'), "data-turbolinks-track" => true | ||
end.reduce(&:concat).html_safe |
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.
Prefer inject over reduce.
app/helpers/reactjs_helper.rb
Outdated
bundle_names = requested_plugins.map { |plugin| Foreman::Plugin.bundle_name plugin } | ||
|
||
bundle_names.map do |name| | ||
javascript_include_tag *webpack_asset_paths(name, :extension => 'js'), "data-turbolinks-track" => true |
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.
Ambiguous splat operator. Parenthesize the method arguments if it's surely a splat operator, or add a whitespace to the right of the * if it should be a multiplication.
3cb8db2
to
bc326dd
Compare
Changes:
import MyComponent from './MyComponent';
componentRegistry.register({ name: 'MyComponent', type: MyComponent });
# my_view.erb
<%= webpacked_plugins_js_for :foreman_ansible, :foreman_openscap %> The arguments of helper are ids that plugins use when registering.
|
@xprazak2 interesting to follow ManageIQ/manageiq-ui-classic#1944 as well, as they implement plugability in the redux concept, i think this two (this pr and manageiq) will complete each other. |
We gave this a shot. Works nicely. |
@@ -77,7 +77,7 @@ | |||
"webpack-stats-plugin": "^0.1.5" | |||
}, | |||
"scripts": { | |||
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ || exit 0", | |||
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ $(./script/foreman_plugins_eslint.js) || exit 0", |
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.
nice :) we should do the same for rubocop
c83509c
to
9e25134
Compare
I fixed the code climate error, is there anything else I should fix? Do you want me to incorporate the change that prevents node modules to appear twice? |
[test katello] |
}, | ||
|
||
registerMultiple(componentObjs) { | ||
return _.forEach(componentObjs, (obj) => { |
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.
maybe just import foreach instead of the entire lodash, e.g.:
import foreach from 'lodash/foreach'
return foreach(componentObjs, (obj) => {)
9e25134
to
24b1175
Compare
I now import just the functions instead of the whole lodash |
@@ -4,4 +4,31 @@ def mount_react_component(name, selector, data = []) | |||
"$(tfm.reactMounter.mount('#{name}', '#{selector}', #{data}));".html_safe | |||
end | |||
end | |||
|
|||
def webpacked_plugins_js_for(*plugin_names) |
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.
@xprazak2 would you mind adding tests for these helpers?
24b1175
to
911ba90
Compare
I added a couple of tests to cover new helpers. |
test/helpers/reactjs_helper_test.rb
Outdated
Foreman::Plugin.register(:foreman_angular) {} | ||
|
||
def webpack_asset_paths(bundle_name, opts) | ||
["<script src=\"https://foreman.example.com:3808/webpack/#{bundle_name}.js\"></script>"] |
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.
shouldnt this be on the same url as the server in prod/test?
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.
Does it matter? It is just a stub for test, the method comes from webpack-rails and I cannot use it directly in test because it complains about missing manifest.
oh, ,right i didn't read it correctly - thanks.
|
911ba90
to
6b49bc8
Compare
[test katello] |
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.
What is here worked well for us after @ohadlevy's change in #4733 (comment) but I don't see that included in the webpack config yet. Will approve after that is added.
@@ -52,11 +50,16 @@ var config = { | |||
path.join(__dirname, '..', 'webpack'), |
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.
Do you mind making the change @ohadlevy mentions in #4733 (comment)?
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.
in that case 👍 from my side to add those changes. /cc @dLobatog as it seems we would need to find a different approach to package 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.
It did indeed broke packaging which means our nightlies are now broken. Given the current load on the packaging and release team it might be wise to slow down these changes, at least until 1.16 is out.
6b49bc8
to
404597e
Compare
I replaced import React from 'react' in plugins as well. |
Thanks @xprazak2! |
This PR removed the fix in http://projects.theforeman.org/issues/20511, which in turn makes Foreman nightlies unable to build again http://koji.katello.org/kojifiles/work/tasks/5312/35312/build.log 'ModuleNotFoundError: Module not found: Error: Can't resolve 'object-assign' in '/usr/lib/node_modules/react/lib''. I'll fix that on another PR. |
@dLobatog as mentioned earlier in this thread, the packaging solution conflict with our plugin assets requirement (as it duplicate the libraries in the bundle across plugins). |
Plugins can register their own react components so that
MountingService
is aware of them.components from plugins can be imported using an alias, so plugins can use components from other plugins:
If you are not sure about alias for plugin, run
script/plugin_webpack_directories.rb
We do not load all js on each page, plugins must make sure their js files are present.
The arguments of helper are ids that plugins use when registering:
TODO: