Skip to content

Commit cfc997c

Browse files
committed
[FR] PUI - Add option for plugins to add header actions
Fixes inventree#8593
1 parent 021063d commit cfc997c

File tree

6 files changed

+62
-3
lines changed

6 files changed

+62
-3
lines changed

src/backend/InvenTree/plugin/base/ui/mixins.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
'template_editor', # Custom template editor
2121
'template_preview', # Custom template preview
2222
'navigation', # Custom navigation items
23+
'primary_action', # Custom primary action buttons
2324
]
2425

2526

@@ -104,6 +105,7 @@ def get_ui_features(
104105
'template_editor': self.get_ui_template_editors,
105106
'template_preview': self.get_ui_template_previews,
106107
'navigation': self.get_ui_navigation_items,
108+
'primary_action': self.get_ui_primary_actions,
107109
}
108110

109111
if feature_type in feature_map:
@@ -186,3 +188,18 @@ def get_ui_navigation_items(
186188
"""
187189
# Default implementation returns an empty list
188190
return []
191+
192+
def get_ui_primary_actions(
193+
self, request: Request, context: dict, **kwargs
194+
) -> list[UIFeature]:
195+
"""Return a list of custom primary action buttons to be injected into the UI.
196+
197+
Args:
198+
request: HTTPRequest object (including user information)
199+
context: Additional context data provided by the UI (query parameters)
200+
201+
Returns:
202+
list: A list of custom primary action buttons to be injected into the UI
203+
"""
204+
# Default implementation returns an empty list
205+
return []

src/backend/InvenTree/plugin/base/ui/tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,13 @@ def test_ui_navigation_items(self):
233233
self.assertEqual(response.data[0]['plugin_name'], 'sampleui')
234234
self.assertEqual(response.data[0]['key'], 'sample-nav-item')
235235
self.assertEqual(response.data[0]['title'], 'Sample Nav Item')
236+
237+
def test_ui_primary_actions(self):
238+
"""Test that the sample UI plugin provides custom primary actions."""
239+
response = self.get(
240+
reverse('api-plugin-ui-feature-list', kwargs={'feature': 'primary_action'})
241+
)
242+
self.assertEqual(1, len(response.data))
243+
self.assertEqual(response.data[0]['plugin_name'], 'sampleui')
244+
self.assertEqual(response.data[0]['key'], 'sample-primary-action')
245+
self.assertEqual(response.data[0]['title'], 'Sample Primary Action')

src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,17 @@ def get_ui_navigation_items(self, request, context, **kwargs):
208208
}
209209
]
210210

211+
def get_ui_primary_action_buttons(self, request, context, **kwargs):
212+
"""Return a list of custom primary action buttons."""
213+
return [
214+
{
215+
'key': 'sample-primary-action',
216+
'title': 'Sample Primary Action',
217+
'icon': 'ti:plus',
218+
'options': {'url': '/sample/action/'},
219+
}
220+
]
221+
211222
def get_admin_context(self) -> dict:
212223
"""Return custom context data which can be rendered in the admin panel."""
213224
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}

src/frontend/src/components/nav/PageDetail.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { useHotkeys } from '@mantine/hooks';
33

44
import { Fragment, type ReactNode, useMemo } from 'react';
55
import { shortenString } from '../../functions/tables';
6+
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
67
import { useUserSettingsState } from '../../states/SettingsState';
78
import { ApiImage } from '../images/ApiImage';
89
import { StylishText } from '../items/StylishText';
10+
import type { PrimaryActionUIFeature } from '../plugins/PluginUIFeatureTypes';
911
import { type Breadcrumb, BreadcrumbList } from './BreadcrumbList';
1012
import PageTitle from './PageTitle';
1113

@@ -97,6 +99,16 @@ export function PageDetail({
9799
}
98100
}, [breadcrumbs, last_crumb, userSettings]);
99101

102+
const extraActions = usePluginUIFeature<PrimaryActionUIFeature>({
103+
featureType: 'primary_action',
104+
context: {}
105+
});
106+
107+
// action caching
108+
const computedActions = useMemo(() => {
109+
return [...(extraActions ?? []), ...(actions ?? [])];
110+
}, [extraActions, actions]);
111+
100112
return (
101113
<>
102114
<PageTitle title={pageTitleString} />
@@ -156,9 +168,9 @@ export function PageDetail({
156168
</Group>
157169
)}
158170
</SimpleGrid>
159-
{actions && (
171+
{computedActions && (
160172
<Group gap={5} justify='right' wrap='nowrap' align='flex-start'>
161-
{actions.map((action, idx) => (
173+
{computedActions.map((action, idx) => (
162174
<Fragment key={idx}>{action}</Fragment>
163175
))}
164176
</Group>

src/frontend/src/components/plugins/PluginUIFeature.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export enum PluginUIFeatureType {
2929
panel = 'panel',
3030
template_editor = 'template_editor',
3131
template_preview = 'template_preview',
32-
navigation = 'navigation'
32+
navigation = 'navigation',
33+
primary_action = 'primary_action'
3334
}
3435

3536
/**

src/frontend/src/components/plugins/PluginUIFeatureTypes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ export type NavigationUIFeature = {
8484
featureContext: {};
8585
featureReturnType: undefined;
8686
};
87+
88+
export type PrimaryActionUIFeature = {
89+
featureType: 'primary_action';
90+
requestContext: {};
91+
responseOptions: PluginUIFeature;
92+
featureContext: {};
93+
featureReturnType: undefined;
94+
};

0 commit comments

Comments
 (0)