Merge branch 'main' into view_protectors

This commit is contained in:
Christopher Broderick 2024-04-29 21:25:36 +01:00 committed by GitHub
commit a6bb99f1e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 84 additions and 29 deletions

View File

@ -10,3 +10,29 @@ You can register webhooks to allow third party apps to be notified of helpdesk e
3. You can optionally set ``HELPDESK_WEBHOOK_TIMEOUT`` which defaults to 3 seconds. Warning, however, webhook requests are sent out sychronously on ticket update. If your webhook handling server is too slow, you should fix this rather than causing helpdesk freezes by messing with this variable. 3. You can optionally set ``HELPDESK_WEBHOOK_TIMEOUT`` which defaults to 3 seconds. Warning, however, webhook requests are sent out sychronously on ticket update. If your webhook handling server is too slow, you should fix this rather than causing helpdesk freezes by messing with this variable.
Once these URLs are configured, a serialized copy of the ticket object will be posted to each of these URLs each time a ticket is created or followed up on respectively. Once these URLs are configured, a serialized copy of the ticket object will be posted to each of these URLs each time a ticket is created or followed up on respectively.
Signals
--------------
Webhooks are triggered through `Django Signals <https://docs.djangoproject.com/en/stable/topics/signals/>_`.
The two available signals are:
- new_ticket_done
- update_ticket_done
You have the opportunity to listen to those in your project if you have post processing workflows outside of webhooks::
from django.dispatch import receiver
from helpdesk.signals import new_ticket_done, update_ticket_done
@receiver(new_ticket_done)
def process_new_ticket(sender, ticket, **kwargs):
"Triggers this code when a ticket is created."
pass
@receiver(update_ticket_done)
def process_followup_update(sender, followup, **kwargs):
"Triggers this code when a follow-up is created."
pass

View File

@ -21,10 +21,10 @@ from email.message import EmailMessage, MIMEPart
from email.utils import getaddresses from email.utils import getaddresses
from email_reply_parser import EmailReplyParser from email_reply_parser import EmailReplyParser
from helpdesk import settings from helpdesk import settings
from helpdesk import webhooks
from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException from helpdesk.exceptions import DeleteIgnoredTicketException, IgnoreTicketException
from helpdesk.lib import process_attachments, safe_template_context from helpdesk.lib import process_attachments, safe_template_context
from helpdesk.models import FollowUp, IgnoreEmail, Queue, Ticket from helpdesk.models import FollowUp, IgnoreEmail, Queue, Ticket
from helpdesk.signals import new_ticket_done, update_ticket_done
import imaplib import imaplib
import logging import logging
import mimetypes import mimetypes
@ -617,8 +617,12 @@ def create_object_from_email_message(message, ticket_id, payload, files, logger)
"Message seems to be auto-reply, not sending any emails back to the sender") "Message seems to be auto-reply, not sending any emails back to the sender")
else: else:
send_info_email(message_id, f, ticket, context, queue, new) send_info_email(message_id, f, ticket, context, queue, new)
if not new: if new:
webhooks.notify_followup_webhooks(f) # emit signal when a new ticket is created
new_ticket_done.send(sender="create_object_from_email_message", ticket=ticket)
else:
# emit signal with followup when the ticket is updated
update_ticket_done.send(sender="create_object_from_email_message", followup=f)
return ticket return ticket

View File

@ -36,6 +36,7 @@ from helpdesk.settings import (
CUSTOMFIELD_TO_FIELD_DICT CUSTOMFIELD_TO_FIELD_DICT
) )
from helpdesk.validators import validate_file_extension from helpdesk.validators import validate_file_extension
from helpdesk.signals import new_ticket_done
import logging import logging
@ -418,6 +419,10 @@ class TicketForm(AbstractTicketForm):
followup.save() followup.save()
files = self._attach_files_to_follow_up(followup) files = self._attach_files_to_follow_up(followup)
# emit signal when the TicketForm.save is done
new_ticket_done.send(sender="TicketForm", ticket=ticket)
self._send_messages(ticket=ticket, self._send_messages(ticket=ticket,
queue=queue, queue=queue,
followup=followup, followup=followup,
@ -507,6 +512,10 @@ class PublicTicketForm(AbstractTicketForm):
followup.save() followup.save()
files = self._attach_files_to_follow_up(followup) files = self._attach_files_to_follow_up(followup)
# emit signal when the PublicTicketForm.save is done
new_ticket_done.send(sender="PublicTicketForm", ticket=ticket)
self._send_messages(ticket=ticket, self._send_messages(ticket=ticket,
queue=queue, queue=queue,
followup=followup, followup=followup,

View File

@ -11,7 +11,6 @@ models.py - Model (and hence database) definitions. This is the core of the
from .lib import format_time_spent, convert_value, daily_time_spent_calculation from .lib import format_time_spent, convert_value, daily_time_spent_calculation
from .templated_email import send_templated_mail from .templated_email import send_templated_mail
from .validators import validate_file_extension from .validators import validate_file_extension
from .webhooks import send_new_ticket_webhook
import datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -644,8 +643,6 @@ class Ticket(models.Model):
if self.queue.enable_notifications_on_email_events: if self.queue.enable_notifications_on_email_events:
for cc in self.ticketcc_set.all(): for cc in self.ticketcc_set.all():
send('ticket_cc', cc.email_address) send('ticket_cc', cc.email_address)
if "new_ticket_cc" in roles:
send_new_ticket_webhook(self)
return recipients return recipients
def _get_assigned_to(self): def _get_assigned_to(self):

7
helpdesk/signals.py Normal file
View File

@ -0,0 +1,7 @@
import django.dispatch
# create a signal for *TicketForm
new_ticket_done = django.dispatch.Signal()
# create a signal for ticket_update view
update_ticket_done = django.dispatch.Signal()

View File

@ -37,7 +37,9 @@ window.addEventListener('load', function()
data.results.forEach(function(ticket) { data.results.forEach(function(ticket) {
$('#ticketsTable tbody').append(` $('#ticketsTable tbody').append(`
<tr> <tr>
<td><a href="/view/?ticket=${ticket.id}&email=${ticket.submitter}&key=${ticket.secret_key}">${ticket.title}</a></td> <td>
<a href='{% url "helpdesk:public_view" %}?ticket=${ticket.id}&email=${ticket.submitter}&key=${ticket.secret_key}'>${ticket.title}</a>
</td>
<td>${ticket.queue.title}</td> <td>${ticket.queue.title}</td>
<td>${ticket.status}</td> <td>${ticket.status}</td>
<td>${ticket.created}</td> <td>${ticket.created}</td>

View File

@ -103,7 +103,10 @@
<dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt> <dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt>
<dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd> <dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd>
<dd class='form_help_text'>{% trans "You can insert ticket and queue details in your message. For more information, see the <a href='../../help/context/'>context help page</a>." %}</dd> {% url "helpdesk:help_context" as context_help_url %}
{% blocktrans %}
<dd class='form_help_text'>You can insert ticket and queue details in your message. For more information, see the <a href='{{ context_help_url }}'>context help page</a>.</dd>
{% endblocktrans %}
{% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %} {% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %}
{% if ticket.status == 1 %} {% if ticket.status == 1 %}

View File

@ -105,7 +105,10 @@
<dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt> <dt><label for='commentBox'>{% trans "Comment / Resolution" %}</label></dt>
<dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd> <dd><textarea rows='8' cols='70' name='comment' id='commentBox'></textarea></dd>
<dd class='form_help_text'>{% trans "You can insert ticket and queue details in your message. For more information, see the <a href='../../help/context/'>context help page</a>." %}</dd> {% url "helpdesk:help_context" as context_help_url %}
{% blocktrans %}
<dd class='form_help_text'>You can insert ticket and queue details in your message. For more information, see the <a href='{{ context_help_url }}'>context help page</a>.</dd>
{% endblocktrans %}
<dt><label>{% trans "New Status" %}</label></dt> <dt><label>{% trans "New Status" %}</label></dt>
{% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %} {% if not ticket.can_be_resolved %}<dd>{% trans "This ticket cannot be resolved or closed until the tickets it depends on are resolved." %}</dd>{% endif %}

View File

@ -15,8 +15,8 @@ from helpdesk.models import (
FollowUp, FollowUp,
Ticket, Ticket,
TicketCC, TicketCC,
TicketChange,
) )
from helpdesk.signals import update_ticket_done
User = get_user_model() User = get_user_model()
@ -268,41 +268,33 @@ def update_ticket(
files = process_attachments(f, files) if files else [] files = process_attachments(f, files) if files else []
if ticket_title and ticket_title != ticket.title: if ticket_title and ticket_title != ticket.title:
c = TicketChange( c = f.ticketchange_set.create(
followup=f,
field=_('Title'), field=_('Title'),
old_value=ticket.title, old_value=ticket.title,
new_value=ticket_title, new_value=ticket_title,
) )
c.save()
ticket.title = ticket_title ticket.title = ticket_title
if new_status != old_status: if new_status != old_status:
c = TicketChange( c = f.ticketchange_set.create(
followup=f,
field=_('Status'), field=_('Status'),
old_value=old_status_str, old_value=old_status_str,
new_value=ticket.get_status_display(), new_value=ticket.get_status_display(),
) )
c.save()
if ticket.assigned_to != old_owner: if ticket.assigned_to != old_owner:
c = TicketChange( c = f.ticketchange_set.create(
followup=f,
field=_('Owner'), field=_('Owner'),
old_value=old_owner, old_value=old_owner,
new_value=ticket.assigned_to, new_value=ticket.assigned_to,
) )
c.save()
if priority != ticket.priority: if priority != ticket.priority:
c = TicketChange( c = f.ticketchange_set.create(
followup=f,
field=_('Priority'), field=_('Priority'),
old_value=ticket.priority, old_value=ticket.priority,
new_value=priority, new_value=priority,
) )
c.save()
ticket.priority = priority ticket.priority = priority
if queue != ticket.queue.id: if queue != ticket.queue.id:
@ -314,13 +306,11 @@ def update_ticket(
ticket.queue_id = queue ticket.queue_id = queue
if due_date != ticket.due_date: if due_date != ticket.due_date:
c = TicketChange( c = f.ticketchange_set.create(
followup=f,
field=_('Due on'), field=_('Due on'),
old_value=ticket.due_date, old_value=ticket.due_date,
new_value=due_date, new_value=due_date,
) )
c.save()
ticket.due_date = due_date ticket.due_date = due_date
for checklist in ticket.checklists.all(): for checklist in ticket.checklists.all():
@ -397,8 +387,9 @@ def update_ticket(
)) ))
ticket.save() ticket.save()
from helpdesk.webhooks import notify_followup_webhooks # emit signal with followup when the ticket update is done
notify_followup_webhooks(f) # internally used for webhooks
update_ticket_done.send(sender="update_ticket", followup=f)
# auto subscribe user if enabled # auto subscribe user if enabled
add_staff_subscription(user, ticket) add_staff_subscription(user, ticket)

View File

@ -1,8 +1,10 @@
from . import settings
import requests import requests
import requests.exceptions import requests.exceptions
import logging import logging
from django.dispatch import receiver
from . import settings
from .signals import new_ticket_done, update_ticket_done
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -10,6 +12,7 @@ def notify_followup_webhooks(followup):
urls = settings.HELPDESK_GET_FOLLOWUP_WEBHOOK_URLS() urls = settings.HELPDESK_GET_FOLLOWUP_WEBHOOK_URLS()
if not urls: if not urls:
return return
# Serialize the ticket associated with the followup # Serialize the ticket associated with the followup
from .serializers import TicketSerializer from .serializers import TicketSerializer
ticket = followup.ticket ticket = followup.ticket
@ -29,6 +32,11 @@ def notify_followup_webhooks(followup):
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
logger.error('Timeout while sending followup webhook to %s', url) logger.error('Timeout while sending followup webhook to %s', url)
# listener is loaded via app.py HelpdeskConfig.ready()
@receiver(update_ticket_done)
def notify_followup_webhooks_receiver(sender, followup, **kwargs):
notify_followup_webhooks(followup)
def send_new_ticket_webhook(ticket): def send_new_ticket_webhook(ticket):
urls = settings.HELPDESK_GET_NEW_TICKET_WEBHOOK_URLS() urls = settings.HELPDESK_GET_NEW_TICKET_WEBHOOK_URLS()
@ -50,3 +58,8 @@ def send_new_ticket_webhook(ticket):
requests.post(url, json=data, timeout=settings.HELPDESK_WEBHOOK_TIMEOUT) requests.post(url, json=data, timeout=settings.HELPDESK_WEBHOOK_TIMEOUT)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
logger.error('Timeout while sending new ticket webhook to %s', url) logger.error('Timeout while sending new ticket webhook to %s', url)
# listener is loaded via app.py HelpdeskConfig.ready()
@receiver(new_ticket_done)
def send_new_ticket_webhook_receiver(sender, ticket, **kwargs):
send_new_ticket_webhook(ticket)