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

8423 Modular plugins #8457

Merged

Conversation

alexander-fedorenko
Copy link
Contributor

@alexander-fedorenko alexander-fedorenko commented Aug 2, 2022

Description

This PR introduces support of module plugins.
See #8423 and https://github.com/geosolutions-it/MapStore2/wiki/%5BProposal%5D-MapStore-Modular-Plugins
for more details.

Please check if the PR fulfills these requirements

What kind of change does this PR introduce? (check one with "x", remove the others)

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

Issue

What is the current behavior?
#8423

What is the new behavior?
MapStore supports module plugins. Plugin implementation is loaded dynamically on demand.

Breaking change

Does this PR introduce a breaking change? (check one with "x", remove the other)

  • Yes, and I documented them in migration notes
  • No

Other useful information

@alexander-fedorenko alexander-fedorenko self-assigned this Aug 2, 2022
Re-use plugins cache whenever hook is used by a new PluginsContainer instance.
Cleanup, making user manager page works again.
@tdipisa tdipisa requested a review from allyoucanmap August 3, 2022 07:36
- Moving Module plugin utils into separate file
- Naming conventions: Lazy loaded plugins are "Module plugins"
MapViewerCmp was dispatching INIT_MAP action unconditionally on component mount. This was happening prior to the dynamically imported epics were registered. Therefore, some epics missed INIT_MAP action and were not running when they should.
This behavior is slightly changed, MapViewerCmp will instead set a state variable holding a function that needs to run whenever plugins are mounted. State value is passed down to the withModulePlugins wrapper of PluginsContainer and function is running whenever PluginsContainer is ready to be rendered.
…ded whenever MapsPlugin is converted into lazy one.

The problem was caused by loadMaps action being dispatched on page mount, again, while dynamically loaded epics are not registered yet.
With the change, action will be dispatched by PluginsContainer, when it is mounted on the page. And this will happen after module plugins are successfully loaded.
@tdipisa tdipisa added the BackportNeeded Commits provided for an issue need to be backported to the milestone's stable branch label Aug 4, 2022
Copy link
Contributor

@allyoucanmap allyoucanmap left a comment

Choose a reason for hiding this comment

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

The implementation seems good, most of the comments are related to naming, questions or typos.
Before to proceed with the issue related with specific plugin I would suggest to complete the support for extension, once we have also that support I'll do another round of test.

We should also verify the final bundles sizes with the webpack analyzer after this initial work

web/client/components/app/main.jsx Outdated Show resolved Hide resolved
@@ -101,10 +101,10 @@ function withExtensions(AppComponent) {
};

render() {
const { plugins, requires } = this.props.pluginsDef;
const { plugins, ...rest } = this.props.pluginsDef;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const { plugins, ...rest } = this.props.pluginsDef;
const { plugins, requires } = this.props.pluginsDef;

web/client/components/app/withExtensions.js Outdated Show resolved Hide resolved
Comment on lines 219 to 224
export default compose(
connect((state) => ({
mode: urlQuery.mode || (urlQuery.mobile || state.browser && state.browser.mobile ? 'mobile' : 'desktop')
})),
withModulePlugins()
)(PluginsContainer);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should add connect or redux in components folder. So please remove all the connect.
Could we also export plugin without HOC?

export const PluginsContainer;

export default withModulePlugins(PluginsContainer);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did you mean we should not add? I removed connect and moved it instead into HOC.
Export with HOC allows to simplify usage of resulting component on a list of pages. In a few places import + usage of connect is replaced now with only import; another places doing connect to a fewer list of state values. It also makes code a bit more error-proof.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need the connect also in the HOC?
We are already connecting everything in the page, both hook, HOC and component should not have connect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As agreed in a call, applied solution with having separate container "ModulePluginsContainer" specifically for MapStore pages. This container is wrapped by a "withModulePlugins" HOC to support processing of module plugins.

}),
monitoredState: getMonitoredState(state, ConfigUtils.getConfigProp('monitorState'))
}))(require('../components/plugins/PluginsContainer').default);
const PluginsContainer = compose(
Copy link
Contributor

Choose a reason for hiding this comment

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

We can remove compose now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, removed

Comment on lines 116 to 118
// const fetchEpic = (storeName = PERSISTED_STORE_NAME, name = 'rootEpic') => {
// return ConfigUtils.getConfigProp(storeName + '.' + name) || {};
// };
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we remove this including the relative doc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed

addEpics: (key, epicsList) => {
muteState[key] = new Subject();
const isolatedEpics = isolateEpics(epicsList, muteState[key].asObservable());
// wrapEpics(isolateEpics(epicsList, muteState[key].asObservable())).forEach(epic => epic$.next(epic));
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this code still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed commented line

web/client/utils/StateUtils.js Show resolved Hide resolved
Comment on lines 48 to 51
this.setState({ initMap: () => {
this.updateMap(id, contextId);
this.setState({initMap: null});
}});
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on the pvt discussion we should do the follow:

  • revert this fix for the moment with all related code around
  • plugin that has still issue because of this should be kept static for the moment
  • once we have the list with all the problematic plugins we can evaluate how to proceed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rolled back, list of plugins is to be added in the comment down below in PR.

@@ -6,138 +6,143 @@
* LICENSE file in the root directory of this source tree.
*/

import {toModulePlugin} from "../utils/ModulePluginsUtils";

Copy link
Contributor

Choose a reason for hiding this comment

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

Please replace require with import for static plugins

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced

…eing loaded whenever MapsPlugin is converted into lazy one. The problem was caused by loadMaps action being dispatched on page mount, again, while dynamically loaded epics are not registered yet. With the change, action will be dispatched by PluginsContainer, when it is mounted on the page. And this will happen after module plugins are successfully loaded."

This reverts commit c50f557
… properly. MapViewerCmp was dispatching INIT_MAP action unconditionally on component mount. This was happening prior to the dynamically imported epics were registered. Therefore, some epics missed INIT_MAP action and were not running when they should. This behavior is slightly changed, MapViewerCmp will instead set a state variable holding a function that needs to run whenever plugins are mounted. State value is passed down to the withModulePlugins wrapper of PluginsContainer and function is running whenever PluginsContainer is ready to be rendered."

This reverts commit 784c483
…Included common values from state into definition so usage of PluginsContainer on page components become shorter and simpler.
@alexander-fedorenko
Copy link
Contributor Author

Bundle analyzer after recent updates

Static plugins included

Parsed

image

Gzipped

image

Static plugins excluded

Parsed

image

Gzipped

image

@allyoucanmap
Copy link
Contributor

@tdipisa @alexander-fedorenko

  1. Added dynamic import for ThematicLayerPlugin and StyleEditorPlugin. I can't test them locally, but they have no epics relying on actions triggered before PluginsContainer get rendered, so both of them should be fine.

We will test this once merged

  1. Applied corrections to the code that mute or unmute epics. Now they will normalize plugins names just like it happens in plugins cache.

ok, good

  1. Configuration for Catalog, Measurement, Annotations is not loaded properly unless reducers are added into list of appReducers. In case of dynamic import default reducer state overwrite configuration passed upon store creation. So either they have to be in appReducers, or default state in reducer should be taken somewhere outside using utility function.

I think we can keep them as appReducers, they haven't effect on app size,
checking the Analyzer results latest branch update is 6.5 MB vs 6.75 MB previous analysis (not Gzipped)

We will open a separate issue to solve the configuration for these plugins

  1. We can use dynamic import, checking with @allyoucanmap if suggested approach is sufficient.

We will open a separate issue to investigate properly on the css-tree import

Test coverage is extended, withModulePlugins & useModulePlugins are covered.

Three updates are on the way:

  • Documentation about transforming extension into module extension.
  • Guideline to update from augmentStore to storeManager use.
  • Making sub-stream mutable when outer epic is muted.

good, waiting for this latest update for the final review, thanks

@tdipisa
Copy link
Member

tdipisa commented Aug 10, 2022

Wonderful @allyoucanmap and @alexander-fedorenko. @alexander-fedorenko I'm looking forward to receiving the last update soon to start the testing phase in DEV with @ElenaGallo.

@alexander-fedorenko
Copy link
Contributor Author

@tdipisa @allyoucanmap Documentation update is committed. There are two new documentation chapters.
I also recovered augmentStore in StateUtils but marked it as deprecated in favor of storeManager usage.

@allyoucanmap allyoucanmap merged commit 4005b4e into geosolutions-it:master Aug 11, 2022
@tdipisa
Copy link
Member

tdipisa commented Aug 11, 2022

@ElenaGallo let's schedule a call to discuss about this and schedule the testing phase in DEV.

@tdipisa
Copy link
Member

tdipisa commented Aug 12, 2022

@ElenaGallo after the test phase the update provided through this PR will need to be backported to 2022.02.xx to be included in the release 2022.02.00

@offtherailz
Copy link
Member

offtherailz commented Aug 16, 2022

Bundle analyzer after recent updates

Static plugins included

Parsed

image

Gzipped

image

Static plugins excluded

Parsed

image

Gzipped

image

Just a note about this:

  • Most of the libs in the main package can be optimized more in the future :
    • QuillJS should be replaced with React-draft-wysiwyg (custom)
    • React-draft-wysiwyg (and so draft.js) should be loaded with React.lazy in every instance. The same for codemirror. This may allow to reduce even more the size of the main JS.
    • pdf.js should be already loaded in a lazy way. For some reason is included in the main package again. Same for shp.js or zip.js. Anyway they look a lot small, so we may need to investigate more about these libs parts.
  • From lighthouse analysis, to increase the score we may need to imrpove caching of JS (expecially for the main JS) introducing the chunk hash in the name (e.g. mapstore2.js --> mapstore2-aaasddgfdgt43.js) Anyway the performance looks to be 2.5 - 3 times better.

Summarizing, pdf.js is the only thing that looks strange to me to see in the main package. The rest of the things can be improved one by one later.

alexander-fedorenko added a commit to alexander-fedorenko/MapStore2 that referenced this pull request Sep 7, 2022
@ElenaGallo ElenaGallo removed the BackportNeeded Commits provided for an issue need to be backported to the milestone's stable branch label Sep 7, 2022
@offtherailz offtherailz added this to the v2022.02.00 milestone Sep 28, 2022
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.

5 participants