diff --git a/docs/docs/extend/plugins/settings.md b/docs/docs/extend/plugins/settings.md index 15d1a5969596..27fd3f7fe838 100644 --- a/docs/docs/extend/plugins/settings.md +++ b/docs/docs/extend/plugins/settings.md @@ -62,7 +62,7 @@ class PluginWithSettings(SettingsMixin, InvenTreePlugin): 'name': _('Assembled Part'), 'description': _('Settings can point to internal database models'), 'model': 'part.part', - 'filters': { + 'model_filters': { 'active': True, 'assembly': True } diff --git a/src/backend/InvenTree/plugin/samples/integration/sample.py b/src/backend/InvenTree/plugin/samples/integration/sample.py index 9d617f19042c..15161713421f 100644 --- a/src/backend/InvenTree/plugin/samples/integration/sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/sample.py @@ -72,14 +72,16 @@ def setup_urls(self): 'default': 'A', }, 'SELECT_COMPANY': { - 'name': 'Company', - 'description': 'Select a company object from the database', + 'name': 'Supplier', + 'description': 'Select a supplier object from the database', 'model': 'company.company', + 'model_filters': {'is_supplier': True}, }, 'SELECT_PART': { 'name': 'Part', 'description': 'Select a part object from the database', 'model': 'part.part', + 'model_filters': {'active': True}, }, 'PROTECTED_SETTING': { 'name': 'Protected Setting', diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index 531317f31bca..d8b642f868d2 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -146,7 +146,7 @@ function NameBadge({ return ; } - // Rendering a user's rame for the badge + // Rendering a user's name for the badge function _render_name() { if (!data || !data.pk) { return ''; diff --git a/src/frontend/src/components/settings/SettingItem.tsx b/src/frontend/src/components/settings/SettingItem.tsx index b6bc866ad30e..3dc6aef71560 100644 --- a/src/frontend/src/components/settings/SettingItem.tsx +++ b/src/frontend/src/components/settings/SettingItem.tsx @@ -9,11 +9,16 @@ import { useMantineColorScheme } from '@mantine/core'; import { IconEdit } from '@tabler/icons-react'; -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import { api } from '../../App'; +import { ModelType } from '../../enums/ModelType'; +import { apiUrl } from '../../states/ApiState'; import type { Setting } from '../../states/states'; import { vars } from '../../theme'; import { Boundary } from '../Boundary'; +import { RenderInstance } from '../render/Instance'; +import { ModelInformationDict } from '../render/ModelType'; /** * Render a single setting value @@ -44,12 +49,61 @@ function SettingValue({ return value; }, [setting]); + const [modelInstance, setModelInstance] = useState(null); + + // Does this setting map to an internal database model? + const modelType: ModelType | null = useMemo(() => { + if (setting.model_name) { + const model = setting.model_name.split('.')[1]; + return ModelType[model as keyof typeof ModelType] || null; + } + return null; + }, [setting]); + + useEffect(() => { + setModelInstance(null); + + if (modelType && setting.value) { + const endpoint = ModelInformationDict[modelType].api_endpoint; + + api + .get(apiUrl(endpoint, setting.value)) + .then((response) => { + if (response.data) { + setModelInstance(response.data); + } else { + setModelInstance(null); + } + }) + .catch((error) => { + setModelInstance(null); + }); + } + }, [setting, modelType]); + + // If a full model instance is available, render it + if (modelInstance && modelType && setting.value) { + return ( + + + + + ); + } + switch (setting?.type || 'string') { case 'boolean': return ( onToggle(setting, event.currentTarget.checked)} style={{ @@ -61,12 +115,20 @@ function SettingValue({ return valueText ? ( - ) : ( - ); diff --git a/src/frontend/src/tables/Search.tsx b/src/frontend/src/tables/Search.tsx index 1d847da40939..3f6acc7dd327 100644 --- a/src/frontend/src/tables/Search.tsx +++ b/src/frontend/src/tables/Search.tsx @@ -22,6 +22,7 @@ export function TableSearchInput({ } placeholder={t`Search`} onChange={(event) => setValue(event.target.value)} diff --git a/src/frontend/tests/pui_plugins.spec.ts b/src/frontend/tests/pui_plugins.spec.ts index 887a21fe6a49..09d52e690437 100644 --- a/src/frontend/tests/pui_plugins.spec.ts +++ b/src/frontend/tests/pui_plugins.spec.ts @@ -1,9 +1,54 @@ import test from 'playwright/test'; -import { loadTab, navigate } from './helpers.js'; +import { clearTableFilters, loadTab, navigate } from './helpers.js'; import { doQuickLogin } from './login.js'; import { setPluginState, setSettingState } from './settings.js'; +// Unit test for plugin settings +test('Plugins - Settings', async ({ page, request }) => { + await doQuickLogin(page, 'admin', 'inventree'); + + // Ensure that the SampleIntegration plugin is enabled + await setPluginState({ + request, + plugin: 'sample', + state: true + }); + + // Navigate and select the plugin + await navigate(page, 'settings/admin/plugin/'); + await clearTableFilters(page); + await page.getByLabel('table-search-input').fill('integration'); + + await page + .getByRole('row', { name: 'SampleIntegrationPlugin' }) + .getByRole('paragraph') + .click(); + await page.getByRole('button', { name: 'Plugin Information' }).click(); + await page + .getByLabel('Plugin Detail -') + .getByRole('button', { name: 'Plugin Settings' }) + .waitFor(); + + // Edit numerical value + await page.getByLabel('edit-setting-NUMERICAL_SETTING').click(); + const originalValue = await page.getByLabel('number-field-value').innerText(); + await page + .getByLabel('number-field-value') + .fill(originalValue == '999' ? '1000' : '999'); + await page.getByRole('button', { name: 'Submit' }).click(); + + // Change it back + await page.getByLabel('edit-setting-NUMERICAL_SETTING').click(); + await page.getByLabel('number-field-value').fill(originalValue); + await page.getByRole('button', { name: 'Submit' }).click(); + + // Select supplier + await page.getByLabel('edit-setting-SELECT_COMPANY').click(); + await page.getByLabel('related-field-value').fill('mouser'); + await page.getByText('Mouser Electronics').click(); +}); + test('Plugins - Panels', async ({ page, request }) => { await doQuickLogin(page, 'admin', 'inventree');