diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 43f12f7737..e247bbfcb8 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 259 +INVENTREE_API_VERSION = 260 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v260 - 2024-09-26 : https://github.com/inventree/InvenTree/pull/8190 + - Adds facility for server-side context data to be passed to client-side plugins + v259 - 2024-09-20 : https://github.com/inventree/InvenTree/pull/8137 - Implements new API endpoint for enabling custom UI features via plugins diff --git a/src/backend/InvenTree/plugin/base/ui/mixins.py b/src/backend/InvenTree/plugin/base/ui/mixins.py index b8e5a0fecb..fb5ec8eac0 100644 --- a/src/backend/InvenTree/plugin/base/ui/mixins.py +++ b/src/backend/InvenTree/plugin/base/ui/mixins.py @@ -19,6 +19,7 @@ class CustomPanel(TypedDict): label: The label of the panel (required, human readable). icon: The icon of the panel (optional, must be a valid icon identifier). content: The content of the panel (optional, raw HTML). + context: Optional context data (dict / JSON) which will be passed to the front-end rendering function source: The source of the panel (optional, path to a JavaScript file). """ @@ -26,6 +27,7 @@ class CustomPanel(TypedDict): label: str icon: str content: str + context: dict source: str @@ -87,6 +89,7 @@ class UserInterfaceMixin: 'label': 'Panel Title', # The title of the panel (required, human readable) 'icon': 'icon-name', # Icon name (optional, must be a valid icon identifier) 'content': '

Panel content

', # HTML content to be rendered in the panel (optional) + 'context': {'key': 'value'}, # Context data to be passed to the front-end rendering function (optional) 'source': 'static/plugin/panel.js', # Path to a JavaScript file to be loaded (optional) } diff --git a/src/backend/InvenTree/plugin/base/ui/serializers.py b/src/backend/InvenTree/plugin/base/ui/serializers.py index 18c69bb1af..fdfbc67148 100644 --- a/src/backend/InvenTree/plugin/base/ui/serializers.py +++ b/src/backend/InvenTree/plugin/base/ui/serializers.py @@ -18,6 +18,7 @@ class PluginPanelSerializer(serializers.Serializer): # Following fields are optional 'icon', 'content', + 'context', 'source', ] @@ -43,6 +44,10 @@ class PluginPanelSerializer(serializers.Serializer): label=_('Panel Content (HTML)'), required=False, allow_blank=True ) + context = serializers.JSONField( + label=_('Panel Context (JSON)'), required=False, allow_null=True, default=None + ) + source = serializers.CharField( label=_('Panel Source (javascript)'), required=False, allow_blank=True ) diff --git a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py index bbf356a5d7..6876653928 100644 --- a/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py @@ -1,7 +1,11 @@ """Sample plugin which demonstrates user interface integrations.""" +import random +import time + from django.utils.translation import gettext_lazy as _ +from InvenTree.version import INVENTREE_SW_VERSION from part.models import Part from plugin import InvenTreePlugin from plugin.helpers import render_template, render_text @@ -15,7 +19,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug SLUG = 'sampleui' TITLE = 'Sample User Interface Plugin' DESCRIPTION = 'A sample plugin which demonstrates user interface integrations' - VERSION = '1.0' + VERSION = '1.1' SETTINGS = { 'ENABLE_PART_PANELS': { @@ -81,11 +85,18 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug }) # A dynamic panel which will be injected into the UI (loaded from external file) + # Note that we additionally provide some "context" data to the front-end render function if self.get_setting('ENABLE_DYNAMIC_PANEL'): panels.append({ 'name': 'dynamic_panel', 'label': 'Dynamic Part Panel', 'source': '/static/plugin/sample_panel.js', + 'context': { + 'version': INVENTREE_SW_VERSION, + 'plugin_version': self.VERSION, + 'random': random.randint(1, 100), + 'time': time.time(), + }, 'icon': 'part', }) diff --git a/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js b/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js index c0a0f5d1f4..9c382d8206 100644 --- a/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js +++ b/src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js @@ -8,7 +8,7 @@ * as well as dynamically hidden, based on the provided context. */ -export function renderPanel(target, context) { +export function renderPanel(target, data) { if (!target) { console.error("No target provided to renderPanel"); @@ -22,13 +22,22 @@ export function renderPanel(target, context) {

It can be hidden or displayed based on the provided context.


-
Context:
+
Client Context:
+
+
Server Context:
+ `; } diff --git a/src/frontend/src/components/plugins/PluginPanel.tsx b/src/frontend/src/components/plugins/PluginPanel.tsx index 153a4cd1ab..431c9ba98c 100644 --- a/src/frontend/src/components/plugins/PluginPanel.tsx +++ b/src/frontend/src/components/plugins/PluginPanel.tsx @@ -13,6 +13,7 @@ export type PluginPanelProps = { label: string; icon?: string; content?: string; + context?: any; source?: string; }; @@ -82,7 +83,9 @@ export default function PluginPanelContent({ func(ref.current, pluginContext); setError(''); } catch (error) { - setError(t`Error occurred while rendering plugin content`); + setError( + t`Error occurred while rendering plugin content: ${error}` + ); } } else { setError(t`Plugin did not provide panel rendering function`); diff --git a/src/frontend/src/hooks/UsePluginPanels.tsx b/src/frontend/src/hooks/UsePluginPanels.tsx index 3f991f86f9..1cf87f19ba 100644 --- a/src/frontend/src/hooks/UsePluginPanels.tsx +++ b/src/frontend/src/hooks/UsePluginPanels.tsx @@ -111,6 +111,11 @@ export function usePluginPanels({ ); const isHidden: boolean = panelState[identifier] ?? true; + const pluginContext: any = { + ...contextData, + context: props.context + }; + return { name: identifier, label: props.label, @@ -118,7 +123,7 @@ export function usePluginPanels({ content: ( ), hidden: isHidden