mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +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:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 [] | ||||
|   | ||||
| @@ -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 | ||||
|     ) | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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'} | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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' | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -76,3 +76,11 @@ export type TemplatePreviewUIFeature = { | ||||
|   }; | ||||
|   featureReturnType: undefined; | ||||
| }; | ||||
|  | ||||
| export type NavigationUIFeature = { | ||||
|   featureType: 'navigation'; | ||||
|   requestContext: {}; | ||||
|   responseOptions: PluginUIFeature; | ||||
|   featureContext: {}; | ||||
|   featureReturnType: undefined; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user