mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	[Refactor] Notification plugins (#9735)
* Refactor notification concept - Notifications handled by plugins * Cleanup * Only send email if template provided in context * Logic cleanup * Fix log_error call * Refactor error logging - Ensure plugin slug is correctly attached - Consistent format - Logic fixes * More robust plugin lookup * Refactor calls to tringger_notification * Tweak for build stock notification * Low stock notification refactor - Actually *use* the notification system - Fix for email template * Check stock only when build is issued * Updated documentation * Add PluginUserSetting class - Allows plugins to define per-user settings * Add API endpoints for PluginUserSetting model * Placeholder for user-plugin-settings page * Refactoring frontend code * Placeholder panel * Adds user interface for changing user-specific plugin settings * Tweaks * Remove old model * Update documentation * Playwright tests * Update API version * Fix unit test * Fix removed arg * Fixes for email notifications - Track status of sending notifications - Add helper "activate" method for plugin class - Update unit tests * Fix barcode tests * More unit test fixes * Test fixes * Fix for settings models with extra fields * Enhance unit test * Remove old test file * Check for null target_fnc * Improve DB query efficiency - Provide a flat list of active keys to plugin.is_active - Prevents DB fetching (in certain circumstances) - Add registry.active_plugins() method * Bump query limit up for test - In practice, this API endpoint is ~10 queries * Handle potential errors * Increase query limit for API test * Increase query limit for some tests * Bump API version * Tweak unit test * Tweak unit test * Increased allowed queries * fix user plugin settings * Fix for unit test * Update debug msg * Tweak API * Fix endpoint * Remove "active plugin keys" code * Restore previous behaviour * Fix unit tests * Tweak unit test * Update src/backend/InvenTree/build/tasks.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/backend/InvenTree/plugin/base/integration/NotificationMixin.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Func updates * Format * Add notification settings * Refactor plugin settings groups * Fix func type * Adjust message * Additional unit tests * Additional playwright tests * Additional playwright test --------- Co-authored-by: Matthias Mair <code@mjmair.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
		| @@ -65,6 +65,47 @@ test('Plugins - Settings', async ({ browser, request }) => { | ||||
|   await page.getByText('Mouser Electronics').click(); | ||||
| }); | ||||
|  | ||||
| test('Plugins - User Settings', async ({ browser, request }) => { | ||||
|   const page = await doCachedLogin(browser); | ||||
|  | ||||
|   // Ensure that the SampleIntegration plugin is enabled | ||||
|   await setPluginState({ | ||||
|     request, | ||||
|     plugin: 'sample', | ||||
|     state: true | ||||
|   }); | ||||
|  | ||||
|   // Navigate to user settings | ||||
|   await navigate(page, 'settings/user/'); | ||||
|   await loadTab(page, 'Plugin Settings'); | ||||
|  | ||||
|   // User settings for the "Sample Plugin" should be visible | ||||
|   await page.getByRole('button', { name: 'Sample Plugin' }).click(); | ||||
|  | ||||
|   await page.getByText('User Setting 1').waitFor(); | ||||
|   await page.getByText('User Setting 2').waitFor(); | ||||
|   await page.getByText('User Setting 3').waitFor(); | ||||
|  | ||||
|   // Check for expected setting options | ||||
|   await page.getByLabel('edit-setting-USER_SETTING_3').click(); | ||||
|  | ||||
|   const val = await page.getByLabel('choice-field-value').inputValue(); | ||||
|  | ||||
|   await page.getByLabel('choice-field-value').click(); | ||||
|  | ||||
|   await page.getByRole('option', { name: 'Choice X' }).waitFor(); | ||||
|   await page.getByRole('option', { name: 'Choice Y' }).waitFor(); | ||||
|   await page.getByRole('option', { name: 'Choice Z' }).waitFor(); | ||||
|  | ||||
|   // Change the value of USER_SETTING_3 | ||||
|   await page | ||||
|     .getByRole('option', { name: val == 'Choice X' ? 'Choice Z' : 'Choice X' }) | ||||
|     .click(); | ||||
|   await page.getByRole('button', { name: 'Submit' }).click(); | ||||
|  | ||||
|   await page.getByText('Setting USER_SETTING_3 updated successfully').waitFor(); | ||||
| }); | ||||
|  | ||||
| // Test base plugin functionality | ||||
| test('Plugins - Functionality', async ({ browser }) => { | ||||
|   // Navigate and select the plugin | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { expect, test } from './baseFixtures.js'; | ||||
| import { apiUrl } from './defaults.js'; | ||||
| import { getRowFromCell, loadTab, navigate } from './helpers.js'; | ||||
| import { doCachedLogin } from './login.js'; | ||||
| import { setSettingState } from './settings.js'; | ||||
| import { setPluginState, setSettingState } from './settings.js'; | ||||
|  | ||||
| /** | ||||
|  * Adjust language and color settings | ||||
| @@ -80,6 +80,97 @@ test('Settings - User theme', async ({ browser }) => { | ||||
|   await page.getByLabel('#228be6').click(); | ||||
| }); | ||||
|  | ||||
| test('Settings - User', async ({ browser }) => { | ||||
|   const page = await doCachedLogin(browser, { | ||||
|     username: 'allaccess', | ||||
|     password: 'nolimits', | ||||
|     url: 'settings/user/' | ||||
|   }); | ||||
|  | ||||
|   await loadTab(page, 'Account'); | ||||
|   await page.getByText('Account Details').waitFor(); | ||||
|   await page.getByText('Profile Details').waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Security'); | ||||
|   await page.getByRole('button', { name: 'Single Sign On' }).waitFor(); | ||||
|   await page.getByRole('button', { name: 'Access Tokens' }).waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Display Options'); | ||||
|   await page | ||||
|     .getByText('The navbar position is fixed to the top of the screen') | ||||
|     .waitFor(); | ||||
|   await page.getByText('Escape Key Closes Forms').waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Search'); | ||||
|   await page.getByText('Whole Word Search').waitFor(); | ||||
|   await page.getByText('Hide Unavailable Stock Items').waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Notifications'); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree Email Notifications' }) | ||||
|     .waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Reporting'); | ||||
|   await page.getByText('Inline report display').waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Plugin Settings'); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree Email Notifications' }) | ||||
|     .waitFor(); | ||||
| }); | ||||
|  | ||||
| test('Settings - Global', async ({ browser, request }) => { | ||||
|   const page = await doCachedLogin(browser, { | ||||
|     username: 'steven', | ||||
|     password: 'wizardstaff', | ||||
|     url: 'settings/system/' | ||||
|   }); | ||||
|  | ||||
|   // Ensure the "slack" notification plugin is enabled | ||||
|   // This is to ensure it is visible in the "notification" settings tab | ||||
|   await setPluginState({ | ||||
|     request, | ||||
|     plugin: 'inventree-slack-notification', | ||||
|     state: true | ||||
|   }); | ||||
|  | ||||
|   await loadTab(page, 'Server'); | ||||
|   await loadTab(page, 'Authentication'); | ||||
|   await loadTab(page, 'Barcodes'); | ||||
|   await loadTab(page, 'Pricing'); | ||||
|   await loadTab(page, 'Parts'); | ||||
|   await loadTab(page, 'Stock'); | ||||
|  | ||||
|   await loadTab(page, 'Notifications'); | ||||
|   await page | ||||
|     .getByText( | ||||
|       'The settings below are specific to each available notification method' | ||||
|     ) | ||||
|     .waitFor(); | ||||
|  | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree Slack Notifications' }) | ||||
|     .click(); | ||||
|   await page.getByText('Slack incoming webhook url').waitFor(); | ||||
|   await page | ||||
|     .getByText('URL that is used to send messages to a slack channel') | ||||
|     .waitFor(); | ||||
|  | ||||
|   await loadTab(page, 'Plugin Settings'); | ||||
|   await page | ||||
|     .getByText('The settings below are specific to each available plugin') | ||||
|     .waitFor(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree Barcodes Provides' }) | ||||
|     .waitFor(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree PDF label printer' }) | ||||
|     .waitFor(); | ||||
|   await page | ||||
|     .getByRole('button', { name: 'InvenTree Slack Notifications' }) | ||||
|     .waitFor(); | ||||
| }); | ||||
|  | ||||
| test('Settings - Admin', async ({ browser }) => { | ||||
|   // Note here we login with admin access | ||||
|   const page = await doCachedLogin(browser, { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user