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

feat: New / Refactor "Nav Mixin" (#9283)

* [FR/P-UI] New / Refactor "Nav Mixin"
Fixes #5269

* remove logging

* fix sample item that causes issues

* Add test coverage

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

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

* [FR/P-UI] New / Refactor "Nav Mixin"
Fixes #5269

* fix style

* remove requirement for source

* fix import

* bump api version

---------

Co-authored-by: Lukas <76838159+wolflu05@users.noreply.github.com>
This commit is contained in:
Matthias Mair 2025-04-20 03:22:58 +02:00 committed by GitHub
parent 058aa190d9
commit 6b0a082b5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 84 additions and 12 deletions

View File

@ -1,13 +1,16 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 338
INVENTREE_API_VERSION = 339
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v339 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9283
- Remove need for source in /plugins/ui/features
v338 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9333
- Adds oAuth2 support for the API

View File

@ -19,6 +19,7 @@ FeatureType = Literal[
'panel', # Custom panels
'template_editor', # Custom template editor
'template_preview', # Custom template preview
'navigation', # Custom navigation items
]
@ -102,6 +103,7 @@ class UserInterfaceMixin:
'panel': self.get_ui_panels,
'template_editor': self.get_ui_template_editors,
'template_preview': self.get_ui_template_previews,
'navigation': self.get_ui_navigation_items,
}
if feature_type in feature_map:
@ -169,3 +171,18 @@ class UserInterfaceMixin:
"""
# Default implementation returns an empty list
return []
def get_ui_navigation_items(
self, request: Request, context: dict, **kwargs
) -> list[UIFeature]:
"""Return a list of custom navigation items to be injected into the UI.
Args:
request: HTTPRequest object (including user information)
context: Additional context data provided by the UI (query parameters)
Returns:
list: A list of custom navigation items to be injected into the UI
"""
# Default implementation returns an empty list
return []

View File

@ -61,5 +61,5 @@ class PluginUIFeatureSerializer(serializers.Serializer):
context = serializers.DictField(label=_('Feature Context'), default=None)
source = serializers.CharField(
label=_('Feature Source (javascript)'), required=True, allow_blank=False
label=_('Feature Source (javascript)'), required=False, allow_blank=True
)

View File

@ -223,3 +223,13 @@ class UserInterfaceMixinTests(InvenTreeAPITestCase):
# Set the setting back to True for subsequent tests
InvenTreeSetting.set_setting('ENABLE_PLUGINS_INTERFACE', True, change_user=None)
def test_ui_navigation_items(self):
"""Test that the sample UI plugin provides custom navigation items."""
response = self.get(
reverse('api-plugin-ui-feature-list', kwargs={'feature': 'navigation'})
)
self.assertEqual(1, len(response.data))
self.assertEqual(response.data[0]['plugin_name'], 'sampleui')
self.assertEqual(response.data[0]['key'], 'sample-nav-item')
self.assertEqual(response.data[0]['title'], 'Sample Nav Item')

View File

@ -197,6 +197,17 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
}
]
def get_ui_navigation_items(self, request, context, **kwargs):
"""Return a list of custom navigation items."""
return [
{
'key': 'sample-nav-item',
'title': 'Sample Nav Item',
'icon': 'ti:menu',
'options': {'url': '/sample/page/'},
}
]
def get_admin_context(self) -> dict:
"""Return custom context data which can be rendered in the admin panel."""
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}

View File

@ -7,8 +7,7 @@ import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import type { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import { navigateToLink } from '@lib/functions/Navigation';
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
import type { TableFilter } from '@lib/types/Filters';
import { t } from '@lingui/core/macro';
import { ActionIcon, Group, Text } from '@mantine/core';

View File

@ -1,3 +1,7 @@
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import { navigateToLink } from '@lib/functions/Navigation';
import { t } from '@lingui/core/macro';
import {
ActionIcon,
Container,
@ -12,13 +16,10 @@ import { IconBell, IconSearch } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useMatch, useNavigate } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import { navigateToLink } from '@lib/functions/Navigation';
import { t } from '@lingui/core/macro';
import { api } from '../../App';
import type { NavigationUIFeature } from '../../components/plugins/PluginUIFeatureTypes';
import { getNavTabs } from '../../defaults/links';
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
import * as classes from '../../main.css';
import { useServerApiState } from '../../states/ApiState';
import { useLocalState } from '../../states/LocalState';
@ -180,10 +181,18 @@ function NavTabs() {
[userSettings]
);
const extraNavs = usePluginUIFeature<NavigationUIFeature>({
featureType: 'navigation',
context: {}
});
const tabs: ReactNode[] = useMemo(() => {
const _tabs: ReactNode[] = [];
navTabs.forEach((tab) => {
const mainNavTabs = getNavTabs(user);
// static content
mainNavTabs.forEach((tab) => {
if (tab.role && !user.hasViewRole(tab.role)) {
return;
}
@ -206,9 +215,23 @@ function NavTabs() {
</Tabs.Tab>
);
});
// dynamic content
extraNavs.forEach((nav) => {
_tabs.push(
<Tabs.Tab
value={nav.options.title}
key={nav.options.key}
onClick={(event: any) =>
navigateToLink(nav.options.options.url, navigate, event)
}
>
{nav.options.title}
</Tabs.Tab>
);
});
return _tabs;
}, [navTabs, user, withIcons]);
}, [extraNavs, navTabs, user, withIcons]);
return (
<Tabs

View File

@ -28,7 +28,8 @@ export enum PluginUIFeatureType {
dashboard = 'dashboard',
panel = 'panel',
template_editor = 'template_editor',
template_preview = 'template_preview'
template_preview = 'template_preview',
navigation = 'navigation'
}
/**

View File

@ -76,3 +76,11 @@ export type TemplatePreviewUIFeature = {
};
featureReturnType: undefined;
};
export type NavigationUIFeature = {
featureType: 'navigation';
requestContext: {};
responseOptions: PluginUIFeature;
featureContext: {};
featureReturnType: undefined;
};