Skip to content

Notification Improvements #9721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions docs/docs/concepts/notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: Notifications
---

## Notifications System

InvenTree provides a notification system which allows users to receive notifications about various events that occur within the system.

## Notification Types

### User Interface

Notifications are displayed in the user interface. New notifications are announced in the header.
{% with id="notification_header", url="part/notification_header.png", description="One new notification in the header" %}
{% include 'img.html' %}
{% endwith %}

They can be viewed in a flyout.
{% with id="notification_flyout", url="part/notification_flyout.png", description="One new notification in the flyout" %}
{% include 'img.html' %}
{% endwith %}

All current notifications are listed in the inbox.
{% with id="notification_inbox", url="part/notification_inbox.png", description="One new notification in the notification inbox" %}
{% include 'img.html' %}
{% endwith %}

All past notification are listed in the history. They can be deleted one-by-one or all at once from there.
{% with id="notification_history", url="part/notification_history.png", description="One old notification in the notification history" %}
{% include 'img.html' %}
{% endwith %}

### Email

Users can also receive notifications via email. This is particularly useful for important events that require immediate attention.

!!! warning "Email Configuration Required"
External notifications require correct [email configuration](../start/config.md#email-settings). They also need to be enabled in the settings under notifications`.

!!! warning "Valid Email Address"
Each user must have a valid email address associated with their account to receive email notifications

### Third-Party Integrations

In addition to the built-in notification system, InvenTree can be integrated with third-party services such as Slack or Discord to send notifications. This allows users to receive real-time updates in their preferred communication channels.

Third party integrations rely on the [plugin system](../plugins/index.md) to provide the necessary functionality.

## Notification Options

The following [global settings](../settings/global.md) control the notification system:

| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("NOTIFICATIONS_ENABLE") }}
{{ globalsetting("NOTIFICATIONS_ERRORS") }}
{{ globalsetting("NOTIFICATIONS_LOW_STOCK") }}
{{ globalsetting("INVENTREE_DELETE_NOTIFICATIONS_DAYS") }}

## Notification Categories

### Error Notifications

If enabled, InvenTree can send notifications about errors that occur within the system. This is useful for administrators to monitor the health of the application and address issues promptly.

!!! info "Staff Only"
Error notifications are only sent to staff users. Regular users will not receive these notifications.

### Part Notifications

There are many types of notifications associated with parts in InvenTree. These notifications can be configured to alert users about various events, such as low stock levels, build orders, and more.

Read the [part notification documentation](../part/notification.md) for more details.
32 changes: 2 additions & 30 deletions docs/docs/part/notification.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,10 @@
title: Part Notifications
---

## General Notification Details

Users can select to receive notifications when certain events occur.

!!! warning "Email Configuration Required"
External notifications require correct [email configuration](../start/config.md#email-settings). They also need to be enabled in the settings under notifications`.

!!! warning "Valid Email Address"
Each user must have a valid email address associated with their account to receive email notifications

Notifications are also shown in the user interface. New notifications are announced in the header.
{% with id="notification_header", url="part/notification_header.png", description="One new notification in the header" %}
{% include 'img.html' %}
{% endwith %}

They can be viewed in a flyout.
{% with id="notification_flyout", url="part/notification_flyout.png", description="One new notification in the flyout" %}
{% include 'img.html' %}
{% endwith %}

All current notifications are listed in the inbox.
{% with id="notification_inbox", url="part/notification_inbox.png", description="One new notification in the notification inbox" %}
{% include 'img.html' %}
{% endwith %}

All past notification are listed in the history. They can be deleted one-by-one or all at once from there.
{% with id="notification_history", url="part/notification_history.png", description="One old notification in the notification history" %}
{% include 'img.html' %}
{% endwith %}

## Part Notification Events

Multiple events can trigger [notifications](../concepts/notifications.md) related to *Parts* in InvenTree. These notifications can be delivered via the user interface, email, or third-party integrations.

### Low Stock Notification

If the *minimum stock* threshold is set for a *Part*, then a "low stock" notification can be generated when the stock level for that part falls below the configured level.
Expand Down
1 change: 0 additions & 1 deletion docs/docs/settings/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ Configuration of basic server settings:
{{ globalsetting("INVENTREE_BACKUP_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_TASKS_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_ERRORS_DAYS") }}
{{ globalsetting("INVENTREE_DELETE_NOTIFICATIONS_DAYS") }}


### Login Settings
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ nav:
- Physical Units: concepts/units.md
- Companies: concepts/company.md
- Custom States: concepts/custom_states.md
- Notifications: concepts/notifications.md
- Pricing: concepts/pricing.md
- Project Codes: concepts/project_codes.md
- Barcodes:
Expand Down
7 changes: 7 additions & 0 deletions src/backend/InvenTree/InvenTree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,13 @@ def notify_staff_users_of_error(instance, label: str, context: dict):
"""Helper function to notify staff users of an error."""
import common.models
import common.notifications
import common.settings

# Return early if error notifications are not enabled
if not common.settings.get_global_setting(
'NOTIFICATIONS_ERRORS', backup_value=True
):
return

try:
# Get all staff users
Expand Down
5 changes: 5 additions & 0 deletions src/backend/InvenTree/common/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import common.models
import InvenTree.helpers
from common.settings import notifications_enabled
from InvenTree.ready import isImportingData, isRebuildingData
from plugin import registry
from plugin.models import NotificationUserSetting, PluginConfig
Expand Down Expand Up @@ -374,6 +375,10 @@ def trigger_notification(
if isImportingData() or isRebuildingData():
return

# Ignore if notifications are disabled globally
if not notifications_enabled():
return

targets = kwargs.get('targets')
target_fnc = kwargs.get('target_fnc')
target_args = kwargs.get('target_args', [])
Expand Down
19 changes: 18 additions & 1 deletion src/backend/InvenTree/common/setting/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import build.validators
import common.currency
import common.models
import common.validators
import order.validators
import report.helpers
Expand Down Expand Up @@ -309,6 +308,24 @@ def __call__(self, value):
'units': _('days'),
'validator': [int, MinValueValidator(7)],
},
'NOTIFICATIONS_ENABLE': {
'name': _('Enable Notifications'),
'description': _('Enable user notifications for system events'),
'default': True,
'validator': bool,
},
'NOTIFICATIONS_ERRORS': {
'name': _('Error Notifications'),
'description': _('Enable notifications for system errors'),
'default': True,
'validator': bool,
},
'NOTIFICATIONS_LOW_STOCK': {
'name': _('Low Stock Notifications'),
'description': _('Enable notifications when a part is low on stock'),
'default': True,
'validator': bool,
},
'INVENTREE_DELETE_NOTIFICATIONS_DAYS': {
'name': _('Notification Deletion Interval'),
'description': _(
Expand Down
19 changes: 15 additions & 4 deletions src/backend/InvenTree/common/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,25 @@ def set_global_setting(key, value, change_user=None, create=True, **kwargs):
return InvenTreeSetting.set_setting(key, value, **kwargs)


def stock_expiry_enabled():
"""Returns True if the stock expiry feature is enabled."""
def stock_expiry_enabled() -> bool:
"""Helper function which returns True if the stock expiry feature is enabled."""
from common.models import InvenTreeSetting

return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY', False, create=False)
return InvenTreeSetting.get_setting(
'STOCK_ENABLE_EXPIRY', backup_value=False, create=False
)


def notifications_enabled() -> bool:
"""Helper function which returns True if the notifications feature is enabled."""
from common.models import InvenTreeSetting

return InvenTreeSetting.get_setting(
'NOTIFICATIONS_ENABLE', backup_value=True, create=False
)


def prevent_build_output_complete_on_incompleted_tests():
def prevent_build_output_complete_on_incompleted_tests() -> bool:
"""Returns True if the completion of the build outputs is disabled until the required tests are passed."""
from common.models import InvenTreeSetting

Expand Down
16 changes: 13 additions & 3 deletions src/backend/InvenTree/part/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@
import common.currency
import common.notifications
import company.models
import InvenTree.helpers
import InvenTree.helpers_model
import InvenTree.tasks
import part.models as part_models
import part.stocktake
from common.settings import get_global_setting
from common.settings import get_global_setting, notifications_enabled
from InvenTree.tasks import (
ScheduledTask,
check_daily_holdoff,
Expand All @@ -35,6 +34,9 @@ def notify_low_stock(part: part_models.Part):
- Triggered when the available stock for a given part falls be low the configured threhsold
- A notification is delivered to any users who are 'subscribed' to this part
"""
if not get_global_setting('NOTIFICATIONS_LOW_STOCK', backup_value=True):
return

name = _('Low stock notification')
message = _(
f'The available stock for {part.name} has fallen below the configured minimum level'
Expand All @@ -52,11 +54,19 @@ def notify_low_stock(part: part_models.Part):
)


def notify_low_stock_if_required(part_id: int):
def notify_low_stock_if_required(part_id: int) -> None:
"""Check if the stock quantity has fallen below the minimum threshold of part.

If true, notify the users who have subscribed to the part
"""
# Return early if notifications are not enabled
# This prevents additional tasks from being queued unnecessarily
if not notifications_enabled():
return

if not get_global_setting('NOTIFICATIONS_LOW_STOCK', backup_value=True):
return

try:
part = part_models.Part.objects.get(pk=part_id)
except part_models.Part.DoesNotExist:
Expand Down
23 changes: 20 additions & 3 deletions src/backend/InvenTree/part/test_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -811,15 +811,16 @@ def _notification_run(self, run_class=None):
self.part.set_starred(self.user, True)
self.part.save()

# There should be 1 (or 2) notifications - in some cases an error is generated, which creates a subsequent notification
self.assertIn(NotificationEntry.objects.all().count(), [1, 2])


class PartNotificationTest(BaseNotificationIntegrationTest):
"""Integration test for part notifications."""

def test_notification(self):
"""Test that a notification is generated."""
# Ensure notifications are enabled
set_global_setting('NOTIFICATIONS_ENABLE', True)
set_global_setting('NOTIFICATIONS_LOW_STOCK', True)

self._notification_run(UIMessageNotification)

# There should be 1 notification message right now
Expand All @@ -830,3 +831,19 @@ def test_notification(self):

# There should not be more messages
self.assertEqual(NotificationMessage.objects.all().count(), 1)

def test_disabled(self):
"""Test that the notification is not generated if notifications are globally disabled."""
set_global_setting('NOTIFICATIONS_ENABLE', False)
set_global_setting('NOTIFICATIONS_LOW_STOCK', True)

self._notification_run(UIMessageNotification)
self.assertEqual(NotificationEntry.objects.all().count(), 0)

def test_disabled_low_stock(self):
"""Test that the notification is not generated if low stock notifications are disabled."""
set_global_setting('NOTIFICATIONS_ENABLE', True)
set_global_setting('NOTIFICATIONS_LOW_STOCK', False)

self._notification_run(UIMessageNotification)
self.assertEqual(NotificationEntry.objects.all().count(), 0)
38 changes: 20 additions & 18 deletions src/frontend/src/components/nav/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,26 @@ export function Header() {
</Tooltip>
<SpotlightButton />
{globalSettings.isSet('BARCODE_ENABLE') && <ScanButton />}
<Indicator
radius='lg'
size='18'
label={notificationCount}
color='red'
disabled={notificationCount <= 0}
inline
>
<Tooltip position='bottom-end' label={t`Notifications`}>
<ActionIcon
onClick={openNotificationDrawer}
variant='transparent'
aria-label='open-notifications'
>
<IconBell />
</ActionIcon>
</Tooltip>
</Indicator>
{globalSettings.isSet('NOTIFICATIONS_ENABLE') && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are we notifying superusers about errors with this disabled?

<Indicator
radius='lg'
size='18'
label={notificationCount}
color='red'
disabled={notificationCount <= 0}
inline
>
<Tooltip position='bottom-end' label={t`Notifications`}>
<ActionIcon
onClick={openNotificationDrawer}
variant='transparent'
aria-label='open-notifications'
>
<IconBell />
</ActionIcon>
</Tooltip>
</Indicator>
)}
<Alerts />
<MainMenu />
</Group>
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/src/components/nav/NavigationDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ function DrawerContent({ closeFunc }: Readonly<{ closeFunc?: () => void }>) {
id: 'notifications',
title: t`Notifications`,
link: '/notifications',
icon: 'notification'
icon: 'notification',
hidden: !globalSettings.isSet('NOTIFICATIONS_ENABLE')
},
{
id: 'user-settings',
Expand All @@ -163,7 +164,7 @@ function DrawerContent({ closeFunc }: Readonly<{ closeFunc?: () => void }>) {
hidden: !user.isStaff()
}
];
}, [user]);
}, [globalSettings, user]);

const menuItemsDocumentation: MenuLinkItem[] = useMemo(
() => DocumentationLinks(),
Expand Down
Loading
Loading