2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

[PUI] Plugin panel context (#8190)

* Add server-side context for panel plugin rendering

* Add "context" to PluginContext type

* Pass server context through to client-side rendering

* Bump API version
This commit is contained in:
Oliver 2024-09-26 20:30:51 +10:00 committed by GitHub
parent 35362347a7
commit 194640f55a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 49 additions and 10 deletions

View File

@ -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

View File

@ -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': '<p>Panel content</p>', # 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)
}

View File

@ -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
)

View File

@ -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',
})

View File

@ -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) {
<p>It can be hidden or displayed based on the provided context.</p>
<hr>
<h5>Context:</h5>
<h5>Client Context:</h5>
<ul>
<li>Username: ${context.user.username()}</li>
<li>Is Staff: ${context.user.isStaff() ? "YES": "NO"}</li>
<li>Model Type: ${context.model}</li>
<li>Instance ID: ${context.id}</li>
<li>Username: ${data.user.username()}</li>
<li>Is Staff: ${data.user.isStaff() ? "YES": "NO"}</li>
<li>Model Type: ${data.model}</li>
<li>Instance ID: ${data.id}</li>
</ul>
<hr>
<h5>Server Context:</h5>
<ul>
<li>Server Version: ${data.context.version}</li>
<li>Plugin Version: ${data.context.plugin_version}</li>
<li>Random Number: ${data.context.random}</li>
<li>Time: ${data.context.time}</li>
</ul>
`;
}

View File

@ -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`);

View File

@ -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: (
<PluginPanelContent
pluginProps={props}
pluginContext={contextData}
pluginContext={pluginContext}
/>
),
hidden: isHidden