Skip to content

Commit

Permalink
[PUI] Plugin panel context (#8190)
Browse files Browse the repository at this point in the history
* Add server-side context for panel plugin rendering

* Add "context" to PluginContext type

* Pass server context through to client-side rendering

* Bump API version
  • Loading branch information
SchrodingersGat authored Sep 26, 2024
1 parent 3536234 commit 194640f
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 10 deletions.
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/backend/InvenTree/plugin/base/ui/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ 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).
"""

name: str
label: str
icon: str
content: str
context: dict
source: str


Expand Down Expand Up @@ -87,6 +89,7 @@ def get_ui_panels(
'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)
}
Expand Down
5 changes: 5 additions & 0 deletions src/backend/InvenTree/plugin/base/ui/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Meta:
# Following fields are optional
'icon',
'content',
'context',
'source',
]

Expand All @@ -43,6 +44,10 @@ class Meta:
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
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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': {
Expand Down Expand Up @@ -81,11 +85,18 @@ def get_ui_panels(self, instance_type: str, instance_id: int, request, **kwargs)
})

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

Expand Down
21 changes: 15 additions & 6 deletions src/backend/InvenTree/plugin/samples/static/plugin/sample_panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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>
`;

}
Expand Down
5 changes: 4 additions & 1 deletion src/frontend/src/components/plugins/PluginPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type PluginPanelProps = {
label: string;
icon?: string;
content?: string;
context?: any;
source?: string;
};

Expand Down Expand Up @@ -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`);
Expand Down
7 changes: 6 additions & 1 deletion src/frontend/src/hooks/UsePluginPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,19 @@ export function usePluginPanels({
);
const isHidden: boolean = panelState[identifier] ?? true;

const pluginContext: any = {
...contextData,
context: props.context
};

return {
name: identifier,
label: props.label,
icon: <InvenTreeIcon icon={iconName as InvenTreeIconType} />,
content: (
<PluginPanelContent
pluginProps={props}
pluginContext={contextData}
pluginContext={pluginContext}
/>
),
hidden: isHidden
Expand Down

0 comments on commit 194640f

Please sign in to comment.