-
Notifications
You must be signed in to change notification settings - Fork 12
feat: integrate platzky plugins with static assets and field rendering #351
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
6061061
feat: add platkzy plugin shotcodes to be handled in locations
raven-wing fd6af43
add missing files
raven-wing 7cd80c8
update goodma-frontend version
raven-wing 37bc898
fix: removed dead code
raven-wing d4585cd
updated frontend version
raven-wing 149cc29
fix tests
raven-wing 5463aa4
lint fix
raven-wing bad1483
improved docstrings
raven-wing 7e06531
prepare for better docstring
raven-wing 4cd3599
coverage increased
raven-wing 6046c9f
more coverage
raven-wing e4890c0
fixes
raven-wing 361cf42
tomli fix
raven-wing b90ac35
ignore sofar
raven-wing 6770fd9
fixes sonarqube
raven-wing 2b948c6
fixes sonarqube
raven-wing d3b1c71
fix sonarqube
raven-wing 6f3ce45
fixes after review
raven-wing f568877
fix after review
raven-wing cca1096
remove architecture from docs
raven-wing 577e63d
refactor
raven-wing 8251659
no none
raven-wing 3bf9887
fix linting
raven-wing 28a3221
fix docs cov
raven-wing f0db3d5
fix nonexisting empty fiedlname
raven-wing b6a3cb4
lin fixes
raven-wing 5508fff
remove lint ignores
raven-wing e0ef8c1
little comment
raven-wing 21fafa7
updated docs
raven-wing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| Plugins | ||
| ======= | ||
|
|
||
| Goodmap supports platzky plugins — standalone Python packages that extend | ||
| functionality via shortcodes, custom location field renderers, and module | ||
| federation frontend components. | ||
|
|
||
| Overview | ||
| -------- | ||
|
|
||
| Plugins are discovered automatically through Python entry points | ||
| (``platzky.plugins`` group). Each plugin can: | ||
|
|
||
| * Register shortcodes for blog/content rendering | ||
| * Expose a React component via Module Federation for rendering inside map | ||
| popups | ||
| * Provide static assets served by the Flask backend | ||
|
|
||
| When a plugin-contributed field appears in a location's ``visible_data`` and | ||
| the plugin is configured, the API wraps the field value with | ||
| ``{"scope": "<shortcode_name>", ...}``. The frontend detects the ``scope`` | ||
| key and renders the appropriate plugin component. | ||
|
|
||
| Configuration | ||
| ------------- | ||
|
|
||
| Add the plugin entry to the ``plugins`` list in your data source (e.g. | ||
| ``data.json``): | ||
|
|
||
| .. code-block:: json | ||
|
|
||
| { | ||
| "plugins": [ | ||
| { | ||
| "name": "promocode", | ||
| "config": { | ||
| "text": "Reveal your discount", | ||
| "color": "#e63946" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| Each plugin has its own configuration schema — refer to the plugin's | ||
| documentation for available fields. | ||
|
|
||
| After adding or removing a plugin, restart the Flask server. | ||
|
|
||
| If a plugin is removed from the configuration while a location still has | ||
| fields referencing it, those fields are silently dropped from the API | ||
| response. A debug message is logged: | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| DEBUG:goodmap.formatter:Dropping field 'promocode': unconfigured plugin data ... | ||
|
|
||
| To see these messages, enable debug logging: | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| export FLASK_DEBUG=1 | ||
|
|
||
|
|
||
|
|
||
| Architecture | ||
| ------------ | ||
|
|
||
| Field Resolution Flow | ||
| ~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| 1. Application starts in ``goodmap.py:create_app_from_config()``: | ||
| * Platzky loads plugins configured in the database via ``plugify()`` | ||
| * Each ``ContentTransformerPluginBase`` subclass registers its shortcodes | ||
| in ``app.shortcodes`` | ||
| * ``field_renderers`` is auto-populated from ``app.shortcodes``:: | ||
|
|
||
| for sc_name in app.shortcodes: | ||
| field_renderers.setdefault(sc_name, sc_name) | ||
|
|
||
| 2. When a location's ``visible_data`` includes a field matching a shortcode | ||
| name, ``formatter.py:_apply_field_plugin()`` wraps it: | ||
|
|
||
| * If the field name is in ``field_renderers``: | ||
| ``{"scope": "<shortcode>", ...original fields...}`` | ||
| * If the value is a ``dict`` with a ``code`` key, it is base64-encoded | ||
| for safe transport | ||
| * If the field name is NOT in ``field_renderers``, the value is a ``dict`` | ||
| with a ``code`` key but no ``type``/``scope`` — it is treated as | ||
| unconfigured plugin data and dropped (with a debug log) | ||
|
|
||
| 3. The frontend receives the wrapped value and renders: | ||
|
|
||
| * ``mapCustomTypeToReactComponent`` checks for ``customValue.scope`` | ||
| * Delegates to ``<PluginSlot scope={scope} props={props} />`` | ||
| * ``PluginSlot`` looks up the registered component in the plugin registry | ||
| and renders it with the remaining props | ||
|
|
||
| Module Federation | ||
| ~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Plugin frontend components are loaded as Webpack 5 Module Federation remotes: | ||
|
|
||
| 1. The backend discovers plugin entry points and registers Flask blueprints | ||
| to serve each plugin's ``static/`` directory | ||
| 2. A ``PLUGIN_MANIFEST`` is embedded in ``map.html`` as | ||
| ``window.PLUGIN_MANIFEST`` | ||
| 3. The frontend's ``pluginLoader.js`` reads the manifest, loads each remote, | ||
| initialises the shared scope, and registers the component with the | ||
| ``pluginRegistry`` | ||
| 4. ``PluginSlot`` subscribes to registry changes and re-renders once the | ||
| component is available | ||
|
|
||
| CORS headers (``Access-Control-Allow-Origin: *``) are set on plugin static | ||
| blueprints to allow the frontend dev server to fetch the remote entry. | ||
|
|
||
| Field Lifecycle | ||
| --------------- | ||
|
|
||
| +----------------------+-----------------------------------------------+--------------------------------------------+ | ||
| | Scenario | API Response | Frontend Behaviour | | ||
| +======================+===============================================+============================================+ | ||
| | Plugin configured | ``{"scope": "promocode", "code": "BASE64", | ``PluginSlot`` renders MF component | | ||
| | | "text": "...", "color": "#..."}`` | | | ||
| +----------------------+-----------------------------------------------+--------------------------------------------+ | ||
| | Plugin NOT | Field omitted from response, debug log | Field not displayed at all | | ||
| | configured | written | | | ||
| +----------------------+-----------------------------------------------+--------------------------------------------+ | ||
| | Standard custom type | ``{"type": "hyperlink", "value": "..."}`` | Rendered as link or CTA button | | ||
| +----------------------+-----------------------------------------------+--------------------------------------------+ | ||
|
|
||
| Writing a Plugin | ||
| ---------------- | ||
|
|
||
| Use ``platzky-promocode`` as the reference implementation | ||
| (`source <https://github.com/problematy/platzky-promocode>`_). | ||
|
|
||
| The minimum required steps: | ||
|
|
||
| 1. Subclass ``ContentTransformerPluginBase`` (from ``platzky.plugin.content_transformer``). | ||
| 2. Declare a ``shortcodes`` class variable mapping shortcode name → ``Shortcode`` instance. | ||
| 3. Implement a ``Shortcode`` subclass with ``name``, ``attributes``, and ``render()``. | ||
| 4. Register via a ``pyproject.toml`` entry point in group ``platzky.plugins``. | ||
| 5. *(Optional)* ship a Webpack Module Federation ``remoteEntry.js`` in your package's | ||
| ``static/`` directory — Goodmap will serve it automatically and add it to | ||
| ``PLUGIN_MANIFEST`` (module name must be ``./Button``). | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from platzky.plugin.content_transformer import ContentTransformerPluginBase | ||
| from platzky.shortcodes.shortcode import Shortcode, ShortcodeAttrs | ||
|
|
||
| class _MyShortcode(Shortcode): | ||
| name = "myplugin" | ||
| description = "My plugin shortcode" | ||
| attributes = ShortcodeAttrs([]) | ||
| example = "[myplugin]value[/myplugin]" | ||
|
|
||
| def render(self, attrs: ShortcodeAttrs, content: str) -> str: | ||
| return f"<span>{content}</span>" | ||
|
|
||
| class MyPlugin(ContentTransformerPluginBase): | ||
| shortcodes = {"myplugin": _MyShortcode({})} | ||
|
|
||
| def __init__(self, _config): | ||
| pass | ||
|
|
||
| .. code-block:: toml | ||
|
|
||
| # pyproject.toml | ||
| [tool.poetry.plugins."platzky.plugins"] | ||
| myplugin = "my_package.plugin:MyPlugin" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.