From 246f17113f07f49c4b575c4ced9b0402f644a67a Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 13 Nov 2024 06:49:48 +1100 Subject: [PATCH] Fix for locate item plugins (#8473) * A hook for caching active plugins * Add LocateItemButton * Implement "locate" button for location detail page too * Fix for StockApiMixin.get_serializer - Recent refactoring removed 'path_detail' attribute * Fix offloading of 'locate' * Remove debug msg * Add custom message * Remove force_async call * Add playwright tests --- .../InvenTree/plugin/base/locate/api.py | 6 +- src/backend/InvenTree/plugin/registry.py | 4 +- .../integration/test_scheduled_task.py | 6 +- src/backend/InvenTree/stock/api.py | 1 + .../src/components/buttons/AdminButton.tsx | 11 +-- .../src/components/items/ActionDropdown.tsx | 1 - .../components/plugins/LocateItemButton.tsx | 94 +++++++++++++++++++ src/frontend/src/enums/ApiEndpoints.tsx | 3 + src/frontend/src/hooks/UsePlugins.tsx | 45 +++++++++ src/frontend/src/pages/build/BuildDetail.tsx | 2 +- .../src/pages/company/CompanyDetail.tsx | 2 +- .../pages/company/ManufacturerPartDetail.tsx | 2 +- .../src/pages/company/SupplierPartDetail.tsx | 2 +- .../src/pages/part/CategoryDetail.tsx | 2 +- src/frontend/src/pages/part/PartDetail.tsx | 2 +- .../pages/purchasing/PurchaseOrderDetail.tsx | 2 +- .../src/pages/sales/ReturnOrderDetail.tsx | 2 +- .../src/pages/sales/SalesOrderDetail.tsx | 2 +- .../src/pages/stock/LocationDetail.tsx | 4 +- src/frontend/src/pages/stock/StockDetail.tsx | 4 +- .../src/tables/settings/GroupTable.tsx | 2 +- src/frontend/tests/pui_plugins.spec.ts | 32 +++++++ 22 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 src/frontend/src/components/plugins/LocateItemButton.tsx create mode 100644 src/frontend/src/hooks/UsePlugins.tsx diff --git a/src/backend/InvenTree/plugin/base/locate/api.py b/src/backend/InvenTree/plugin/base/locate/api.py index 86962d543d..8d6414ae55 100644 --- a/src/backend/InvenTree/plugin/base/locate/api.py +++ b/src/backend/InvenTree/plugin/base/locate/api.py @@ -6,7 +6,7 @@ from rest_framework.generics import GenericAPIView from rest_framework.response import Response from InvenTree.tasks import offload_task -from plugin.registry import registry +from plugin.registry import call_plugin_function, registry from stock.models import StockItem, StockLocation @@ -59,7 +59,7 @@ class LocatePluginView(GenericAPIView): StockItem.objects.get(pk=item_pk) offload_task( - registry.call_plugin_function, + call_plugin_function, plugin, 'locate_stock_item', item_pk, @@ -78,7 +78,7 @@ class LocatePluginView(GenericAPIView): StockLocation.objects.get(pk=location_pk) offload_task( - registry.call_plugin_function, + call_plugin_function, plugin, 'locate_stock_location', location_pk, diff --git a/src/backend/InvenTree/plugin/registry.py b/src/backend/InvenTree/plugin/registry.py index 0cfc287d0f..68b77da66f 100644 --- a/src/backend/InvenTree/plugin/registry.py +++ b/src/backend/InvenTree/plugin/registry.py @@ -159,7 +159,7 @@ class PluginsRegistry: # Update the registry hash value self.update_plugin_hash() - def call_plugin_function(self, slug, func, *args, **kwargs): + def call_plugin_function(self, slug: str, func: str, *args, **kwargs): """Call a member function (named by 'func') of the plugin named by 'slug'. As this is intended to be run by the background worker, @@ -807,7 +807,7 @@ class PluginsRegistry: registry: PluginsRegistry = PluginsRegistry() -def call_function(plugin_name, function_name, *args, **kwargs): +def call_plugin_function(plugin_name: str, function_name: str, *args, **kwargs): """Global helper function to call a specific member function of a plugin.""" return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs) diff --git a/src/backend/InvenTree/plugin/samples/integration/test_scheduled_task.py b/src/backend/InvenTree/plugin/samples/integration/test_scheduled_task.py index ae2b173424..b2426a4e06 100644 --- a/src/backend/InvenTree/plugin/samples/integration/test_scheduled_task.py +++ b/src/backend/InvenTree/plugin/samples/integration/test_scheduled_task.py @@ -5,7 +5,7 @@ from django.test import TestCase from plugin import InvenTreePlugin, registry from plugin.helpers import MixinImplementationError from plugin.mixins import ScheduleMixin -from plugin.registry import call_function +from plugin.registry import call_plugin_function class ExampleScheduledTaskPluginTests(TestCase): @@ -67,10 +67,10 @@ class ExampleScheduledTaskPluginTests(TestCase): def test_calling(self): """Check if a function can be called without errors.""" # Check with right parameters - self.assertEqual(call_function('schedule', 'member_func'), False) + self.assertEqual(call_plugin_function('schedule', 'member_func'), False) # Check with wrong key - self.assertEqual(call_function('does_not_exist', 'member_func'), None) + self.assertEqual(call_plugin_function('does_not_exist', 'member_func'), None) class ScheduledTaskPluginTests(TestCase): diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index 62c18c3ff5..0e25e47c11 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -880,6 +880,7 @@ class StockApiMixin: for key in [ 'part_detail', + 'path_detail', 'location_detail', 'supplier_part_detail', 'tests', diff --git a/src/frontend/src/components/buttons/AdminButton.tsx b/src/frontend/src/components/buttons/AdminButton.tsx index 0bd5093077..27a7977f96 100644 --- a/src/frontend/src/components/buttons/AdminButton.tsx +++ b/src/frontend/src/components/buttons/AdminButton.tsx @@ -11,7 +11,7 @@ import { ActionButton } from './ActionButton'; export type AdminButtonProps = { model: ModelType; - pk: number | undefined; + id: number | undefined; }; /* @@ -46,12 +46,12 @@ export default function AdminButton(props: Readonly) { } // No primary key provided - if (!props.pk) { + if (!props.id) { return false; } return true; - }, [user, props.model, props.pk]); + }, [user, props.model, props.id]); const openAdmin = useCallback( (event: any) => { @@ -63,7 +63,7 @@ export default function AdminButton(props: Readonly) { } // Generate the URL for the admin interface - const url = `${host}/${server.server.django_admin}${modelDef.admin_url}${props.pk}/`; + const url = `${host}/${server.server.django_admin}${modelDef.admin_url}${props.id}/`; if (event?.ctrlKey || event?.shiftKey) { // Open the link in a new tab @@ -72,7 +72,7 @@ export default function AdminButton(props: Readonly) { window.open(url, '_self'); } }, - [props.model, props.pk] + [props.model, props.id] ); return ( @@ -80,7 +80,6 @@ export default function AdminButton(props: Readonly) { icon={} color='blue' size='lg' - radius='sm' variant='filled' tooltip={t`Open in admin interface`} hidden={!enabled} diff --git a/src/frontend/src/components/items/ActionDropdown.tsx b/src/frontend/src/components/items/ActionDropdown.tsx index 81dad21e06..ad098e91f0 100644 --- a/src/frontend/src/components/items/ActionDropdown.tsx +++ b/src/frontend/src/components/items/ActionDropdown.tsx @@ -72,7 +72,6 @@ export function ActionDropdown({