mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-27 19:16:44 +00:00
[UI] Settings render (#9148)
* Update sample plugin * Inline rendering of model based settings * Spelling fix * Add playwright testing
This commit is contained in:
parent
2cabd02c6b
commit
8df34cefd6
@ -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
|
||||
}
|
||||
|
@ -72,14 +72,16 @@ class SampleIntegrationPlugin(
|
||||
'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',
|
||||
|
@ -146,7 +146,7 @@ function NameBadge({
|
||||
return <Skeleton height={12} radius='md' />;
|
||||
}
|
||||
|
||||
// Rendering a user's rame for the badge
|
||||
// Rendering a user's name for the badge
|
||||
function _render_name() {
|
||||
if (!data || !data.pk) {
|
||||
return '';
|
||||
|
@ -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<any>(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 (
|
||||
<Group justify='right' gap='xs'>
|
||||
<RenderInstance instance={modelInstance} model={modelType} />
|
||||
<Button
|
||||
aria-label={`edit-setting-${setting.key}`}
|
||||
variant='subtle'
|
||||
onClick={() => onEdit(setting)}
|
||||
>
|
||||
<IconEdit />
|
||||
</Button>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
switch (setting?.type || 'string') {
|
||||
case 'boolean':
|
||||
return (
|
||||
<Switch
|
||||
size='sm'
|
||||
radius='lg'
|
||||
aria-label={`toggle-setting-${setting.key}`}
|
||||
checked={setting.value.toLowerCase() == 'true'}
|
||||
onChange={(event) => onToggle(setting, event.currentTarget.checked)}
|
||||
style={{
|
||||
@ -61,12 +115,20 @@ function SettingValue({
|
||||
return valueText ? (
|
||||
<Group gap='xs' justify='right'>
|
||||
<Space />
|
||||
<Button variant='subtle' onClick={() => onEdit(setting)}>
|
||||
<Button
|
||||
aria-label={`edit-setting-${setting.key}`}
|
||||
variant='subtle'
|
||||
onClick={() => onEdit(setting)}
|
||||
>
|
||||
{valueText}
|
||||
</Button>
|
||||
</Group>
|
||||
) : (
|
||||
<Button variant='subtle' onClick={() => onEdit(setting)}>
|
||||
<Button
|
||||
aria-label={`edit-setting-${setting.key}`}
|
||||
variant='subtle'
|
||||
onClick={() => onEdit(setting)}
|
||||
>
|
||||
<IconEdit />
|
||||
</Button>
|
||||
);
|
||||
|
@ -22,6 +22,7 @@ export function TableSearchInput({
|
||||
<TextInput
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
aria-label='table-search-input'
|
||||
leftSection={<IconSearch />}
|
||||
placeholder={t`Search`}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
|
@ -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');
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user