PUI Plugin Panels (#7470)

* Adds basic API endpoint for requesting plugin panels

* Split PanelType out into own file

* Placeholder for a plugin panel loaded dynamically

* Add some dummy data for the plugin panels

* Example of plugin panel selection based on page

* Expose some global window attributes

* Add new setting

* Disable panel return if plugin integration is not enabled

* Update hook to auto-magically load plugin panels

* Allow custom panel integration for more panel  groups

* Remove debug call

* Tweak query return data

* async fn

* Adds <PluginPanel> component for handling panel render

* Cleanup

* Prevent API requests before instance ID is known

* Pass instance data through

* Framework for a sample plugin which implements custom panels

* offload custom panels to sample plugin

* Load raw HTML content

* Expand custom panel rendering demo

* Adjust API endpoints

* Add function to clear out static files which do not match installed plugin(s)

* Update static files when installing plugins from file

* Update static files when installing or uninstalling a plugin

* Update static files on config change

* Pass more information through to plugin panels

* Prepend hostname to plugin source

* Pass instance detail through

* Cleanup code for passing data through to plugin panels

- Define interface type
- Shorten variable names

* Update docs requirements

* Revert "Update docs requirements"

This reverts commit 63a06d97f5.

* Add placeholder for documentation

* Fix imports

* Add a broken panel which tries to load a non-existent javascript file

* Render error message if plugin does not load correctly

* Only allow superuser to perform plugin actions

* Code cleanup

* Add "dynamic" contnt - javascript file - to example plugin

* Remove default values

* Cleanup unused code

* PanelGroup updates

* Cleanup hooks for changing panel state

* More work needed...

* Code cleanup

* More updates / refactoring

- Allow dynamic hiding of a particular panel
- Pass target ref as positional argument
- Better handling of async calls

* Documentation

* Bump API version

* Provide theme object to plugin context

* Adjust sample plugin

* Docs updates

* Fix includefile call in docs

* Improve type annotation

* Cleanup

* Enable plugin panels for "purchasing index" and "sales index" pages

* Fix for plugin query check

* Improvements to panel selection

- Code refactor / cleanup
- Ensure that a valid panel is always displayed
- Allow plugin panels to persist, even after reload

* Playwright test fixes

* Update src/frontend/src/hooks/UsePluginPanels.tsx

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>

* Update src/frontend/src/components/plugins/PluginPanel.tsx

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>

* Update src/frontend/src/components/plugins/PluginContext.tsx

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>

* Fix context

* Add more context data

* Docs updates

* Reimplement local state

* Fix mkdocs.yml

* Expose 'colorScheme' to plugin context

* Define CustomPanel type definition

* Add unit testing for user interface plugins

* Add front-end tests for plugin panels

* Add new setting to plugin_settings_keys

* Adds helper function for annotating build line allocations

* Improve query efficiency

- Especially around unit testing
- Ensure all settings are generated
- Do not auto-create settings during registry load

* Improve query efficiency for build order operations

* Reduce max query count for specific test

* Revert query count limit

* playwright test updates

---------

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>
This commit is contained in:
Oliver
2024-09-16 10:36:27 +10:00
committed by GitHub
co-authored by Lukas
parent df8269df2a
commit 12d2865b59
56 changed files with 1446 additions and 163 deletions
@@ -0,0 +1,124 @@
"""Sample plugin which demonstrates user interface integrations."""
from django.utils.translation import gettext_lazy as _
from part.models import Part
from plugin import InvenTreePlugin
from plugin.helpers import render_template, render_text
from plugin.mixins import SettingsMixin, UserInterfaceMixin
class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlugin):
"""A sample plugin which demonstrates user interface integrations."""
NAME = 'SampleUI'
SLUG = 'sampleui'
TITLE = 'Sample User Interface Plugin'
DESCRIPTION = 'A sample plugin which demonstrates user interface integrations'
VERSION = '1.0'
SETTINGS = {
'ENABLE_PART_PANELS': {
'name': _('Enable Part Panels'),
'description': _('Enable custom panels for Part views'),
'default': True,
'validator': bool,
},
'ENABLE_PURCHASE_ORDER_PANELS': {
'name': _('Enable Purchase Order Panels'),
'description': _('Enable custom panels for Purchase Order views'),
'default': False,
'validator': bool,
},
'ENABLE_BROKEN_PANELS': {
'name': _('Enable Broken Panels'),
'description': _('Enable broken panels for testing'),
'default': True,
'validator': bool,
},
'ENABLE_DYNAMIC_PANEL': {
'name': _('Enable Dynamic Panel'),
'description': _('Enable dynamic panels for testing'),
'default': True,
'validator': bool,
},
}
def get_custom_panels(self, instance_type: str, instance_id: int, request):
"""Return a list of custom panels to be injected into the UI."""
panels = []
# First, add a custom panel which will appear on every type of page
# This panel will contain a simple message
content = render_text(
"""
This is a <i>sample panel</i> which appears on every page.
It renders a simple string of <b>HTML</b> content.
<br>
<h5>Instance Details:</h5>
<ul>
<li>Instance Type: {{ instance_type }}</li>
<li>Instance ID: {{ instance_id }}</li>
</ul>
""",
context={'instance_type': instance_type, 'instance_id': instance_id},
)
panels.append({
'name': 'sample_panel',
'label': 'Sample Panel',
'content': content,
})
# A broken panel which tries to load a non-existent JS file
if self.get_setting('ENABLE_BROKEN_PANElS'):
panels.append({
'name': 'broken_panel',
'label': 'Broken Panel',
'source': '/this/does/not/exist.js',
})
# A dynamic panel which will be injected into the UI (loaded from external file)
if self.get_setting('ENABLE_DYNAMIC_PANEL'):
panels.append({
'name': 'dynamic_panel',
'label': 'Dynamic Part Panel',
'source': '/static/plugin/sample_panel.js',
'icon': 'part',
})
# Next, add a custom panel which will appear on the 'part' page
# Note that this content is rendered from a template file,
# using the django templating system
if self.get_setting('ENABLE_PART_PANELS') and instance_type == 'part':
try:
part = Part.objects.get(pk=instance_id)
except (Part.DoesNotExist, ValueError):
part = None
# Note: This panel will *only* be available if the part is active
if part and part.active:
content = render_template(
self, 'uidemo/custom_part_panel.html', context={'part': part}
)
panels.append({
'name': 'part_panel',
'label': 'Part Panel',
'content': content,
})
# Next, add a custom panel which will appear on the 'purchaseorder' page
if (
self.get_setting('ENABLE_PURCHASE_ORDER_PANELS')
and instance_type == 'purchaseorder'
):
panels.append({
'name': 'purchase_order_panel',
'label': 'Purchase Order Panel',
'content': 'This is a custom panel which appears on the <b>Purchase Order</b> view page.',
})
return panels