[FR] Slack notifications & notification method updates (#4114)

* [FR] Slack notification
Add slack sending method
Fixes #3843

* Add global panel with notification methods

* add plugin information

* fix plugin lookup and link

* Add settings content mixin

* Add instructions for Slack setup

* fix rendering of custom settings content
This commit is contained in:
Matthias Mair 2022-12-31 23:20:55 +01:00 committed by GitHub
parent 758f7886f1
commit 8b2e2a28d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 189 additions and 5 deletions

View File

@ -192,6 +192,7 @@ class MethodStorageClass:
for item in current_method:
plugin = item.get_plugin(item)
ref = f'{plugin.package_path}_{item.METHOD_NAME}' if plugin else item.METHOD_NAME
item.plugin = plugin() if plugin else None
filtered_list[ref] = item
storage.liste = list(filtered_list.values())

View File

@ -742,3 +742,24 @@ class PanelMixin:
panels.append(panel)
return panels
class SettingsContentMixin:
"""Mixin which allows integration of custom HTML content into a plugins settings page.
The 'get_settings_content' method must return the HTML content to appear in the section
"""
class MixinMeta:
"""Meta for mixin."""
MIXIN_NAME = 'SettingsContent'
def __init__(self):
"""Register mixin."""
super().__init__()
self.add_mixin('settingscontent', True, __class__)
def get_settings_content(self, view, request):
"""This method *must* be implemented by the plugin class."""
raise MixinNotImplementedError(f"{__class__} is missing the 'get_settings_content' method")

View File

@ -3,13 +3,15 @@
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
import requests
from allauth.account.models import EmailAddress
import common.models
import InvenTree.helpers
import InvenTree.tasks
from plugin import InvenTreePlugin
from plugin.mixins import BulkNotificationMethod, SettingsMixin
from plugin import InvenTreePlugin, registry
from plugin.mixins import (BulkNotificationMethod, SettingsContentMixin,
SettingsMixin)
class PlgMixin:
@ -23,7 +25,7 @@ class PlgMixin:
return CoreNotificationsPlugin
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
class CoreNotificationsPlugin(SettingsContentMixin, SettingsMixin, InvenTreePlugin):
"""Core notification methods for InvenTree."""
NAME = "CoreNotificationsPlugin"
@ -39,8 +41,30 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
'default': False,
'validator': bool,
},
'ENABLE_NOTIFICATION_SLACK': {
'name': _('Enable slack notifications'),
'description': _('Allow sending of slack channel messages for event notifications'),
'default': False,
'validator': bool,
},
'NOTIFICATION_SLACK_URL': {
'name': _('Slack incoming webhook url'),
'description': _('URL that is used to send messages to a slack channel'),
'protected': True,
},
}
def get_settings_content(self, request):
"""Custom settings content for the plugin."""
return """
<p>Setup for Slack:</p>
<ol>
<li>Create a new Slack app on <a href="https://api.slack.com/apps/new" target="_blank">this page</a></li>
<li>Enable <i>Incoming Webhooks</i> for the channel you want the notifications posted to</li>
<li>Set the webhook URL in the settings above</li>
<li>Enable the plugin</li>
"""
class EmailNotification(PlgMixin, BulkNotificationMethod):
"""Notificationmethod for delivery via Email."""
@ -94,3 +118,53 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
InvenTree.tasks.send_email(subject, '', targets, html_message=html_message)
return True
class SlackNotification(PlgMixin, BulkNotificationMethod):
"""Notificationmethod for delivery via Slack channel messages."""
METHOD_NAME = 'slack'
METHOD_ICON = 'fa-envelope'
GLOBAL_SETTING = 'ENABLE_NOTIFICATION_SLACK'
def get_targets(self):
"""Not used by this method."""
return self.targets
def send_bulk(self):
"""Send the notifications out via slack."""
instance = registry.plugins.get(self.get_plugin().NAME.lower())
url = instance.get_setting('NOTIFICATION_SLACK_URL')
if not url:
return False
ret = requests.post(url, json={
'text': str(self.context['message']),
'blocks': [
{
"type": "section",
"text": {
"type": "plain_text",
"text": str(self.context['name'])
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": str(self.context['message'])
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": str(_("Open link")), "emoji": True
},
"value": f'{self.category}_{self.obj.pk}',
"url": self.context['link'],
"action_id": "button-action"
}
}]
})
return ret.ok

View File

@ -8,8 +8,8 @@ from ..base.barcodes.mixins import BarcodeMixin
from ..base.event.mixins import EventMixin
from ..base.integration.mixins import (APICallMixin, AppMixin, NavigationMixin,
PanelMixin, ScheduleMixin,
SettingsMixin, UrlsMixin,
ValidationMixin)
SettingsContentMixin, SettingsMixin,
UrlsMixin, ValidationMixin)
from ..base.label.mixins import LabelPrintingMixin
from ..base.locate.mixins import LocateMixin
@ -20,6 +20,7 @@ __all__ = [
'LabelPrintingMixin',
'NavigationMixin',
'ScheduleMixin',
'SettingsContentMixin',
'SettingsMixin',
'UrlsMixin',
'PanelMixin',

View File

@ -29,6 +29,15 @@ def plugin_settings(plugin, *args, **kwargs):
return registry.mixins_settings.get(plugin)
@register.simple_tag(takes_context=True)
def plugin_settings_content(context, plugin, *args, **kwargs):
"""Get the settings content for the plugin."""
plg = registry.get_plugin(plugin)
if hasattr(plg, 'get_settings_content'):
return plg.get_settings_content(context.request)
return None
@register.simple_tag()
def mixin_enabled(plugin, key, *args, **kwargs):
"""Is the mixin registerd and configured in the plugin?"""
@ -71,3 +80,16 @@ def plugin_errors(*args, **kwargs):
def notification_settings_list(context, *args, **kwargs):
"""List of all user notification settings."""
return storage.get_usersettings(user=context.get('user', None))
@register.simple_tag(takes_context=True)
def notification_list(context, *args, **kwargs):
"""List of all notification methods."""
return [{
'slug': a.METHOD_NAME,
'icon': a.METHOD_ICON,
'setting': a.GLOBAL_SETTING,
'plugin': a.plugin,
'description': a.__doc__,
'name': a.__name__
} for a in storage.liste]

View File

@ -0,0 +1,6 @@
{% load plugin_extras %}
{% plugin_settings_content plugin_key as plugin_settings_content %}
{% if plugin_settings_content %}
{{ plugin_settings_content|safe }}
{% endif %}

View File

@ -0,0 +1,54 @@
{% extends "panel.html" %}
{% load i18n %}
{% load inventree_extras %}
{% load plugin_extras %}
{% block label %}global-notifications{% endblock label %}
{% block heading %}{% trans "Global Notification Settings" %}{% endblock heading %}
{% block content %}
<div class='row'>
<table class='table table-striped table-condensed'>
<thead>
<th></th>
<th>{% trans "Name" %}</th>
<th>{% trans "Slug" %}</th>
<th>{% trans "Description" %}</th>
</thead>
<tbody>
{% notification_list as methods %}
{% for method in methods %}
<tr>
<td>{% if method.icon %}<span class="fas {{ method.icon }}"></span>{% endif %}</td>
<td>
{{ method.name }}
{% if method.plugin %}
<a class='sidebar-selector' id='select-plugin-{{method.plugin.slug}}' data-bs-parent="#sidebar">
<span class='badge bg-dark badge-right rounded-pill'>{{ method.plugin.slug }}</span>
</a>
{% endif %}
</td>
<td>{{ method.slug }}</td>
<td>{{ method.description }}</td>
</tr>
{% if method.setting %}
<tr>
<td colspan="1"></td>
<td colspan="3">
<table class='table table-condensed'>
<tbody>
{% include "InvenTree/settings/setting.html" with key=method.setting plugin=method.plugin %}
</tbody>
</table>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@ -148,4 +148,6 @@
{% include 'InvenTree/settings/mixins/urls.html' %}
{% endif %}
{% include 'InvenTree/settings/mixins/settings_content.html' %}
{% endblock %}

View File

@ -32,6 +32,7 @@
{% include "InvenTree/settings/global.html" %}
{% include "InvenTree/settings/login.html" %}
{% include "InvenTree/settings/barcode.html" %}
{% include "InvenTree/settings/notifications.html" %}
{% include "InvenTree/settings/label.html" %}
{% include "InvenTree/settings/report.html" %}
{% include "InvenTree/settings/part.html" %}

View File

@ -32,6 +32,8 @@
{% include "sidebar_item.html" with label='login' text=text icon="fa-fingerprint" %}
{% trans "Barcode Support" as text %}
{% include "sidebar_item.html" with label='barcodes' text=text icon="fa-qrcode" %}
{% trans "Notifications" as text %}
{% include "sidebar_item.html" with label='global-notifications' text=text icon="fa-bell" %}
{% trans "Pricing" as text %}
{% include "sidebar_item.html" with label='pricing' text=text icon="fa-dollar-sign" %}
{% trans "Label Printing" as text %}