mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2024-11-25 01:13:31 +01:00
Merge pull request #1128 from django-helpdesk/catch_exceptions_on_precess_email_loop
Catch exceptions on precess email loop
This commit is contained in:
commit
2b6ad7a2cf
2
.github/workflows/release_to_pypi.yml
vendored
2
.github/workflows/release_to_pypi.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
|||||||
python -m build
|
python -m build
|
||||||
twine check --strict dist/*
|
twine check --strict dist/*
|
||||||
- name: Publish distribution to PyPI
|
- name: Publish distribution to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@master
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
@ -22,6 +22,13 @@ Before django-helpdesk will be much use, you need to do some basic configuration
|
|||||||
If you wish to use `celery` instead of cron, you must add 'django_celery_beat' to `INSTALLED_APPS` and add a periodic celery task through the Django admin.
|
If you wish to use `celery` instead of cron, you must add 'django_celery_beat' to `INSTALLED_APPS` and add a periodic celery task through the Django admin.
|
||||||
|
|
||||||
You will need to create a support queue, and associated login/host values, in the Django admin interface, in order for mail to be picked-up from the mail server and placed in the tickets table of your database. The values in the settings file alone, will not create the necessary values to trigger the get_email function.
|
You will need to create a support queue, and associated login/host values, in the Django admin interface, in order for mail to be picked-up from the mail server and placed in the tickets table of your database. The values in the settings file alone, will not create the necessary values to trigger the get_email function.
|
||||||
|
|
||||||
|
DEBUGGING EMAIL EXTRACTION
|
||||||
|
==========================
|
||||||
|
You can run the management command manually from the command line with additional commands options:
|
||||||
|
**debug_to_stdout** - set this when manually running the command from a terminal so that additional debugging about which queues are being processed is written to stdout (console by default)
|
||||||
|
For example:
|
||||||
|
**/path/to/helpdesksite/manage.py get_email --debug_to_stdout**
|
||||||
|
|
||||||
4. If you wish to automatically escalate tickets based on their age, set up a cronjob to run the escalation command on a regular basis::
|
4. If you wish to automatically escalate tickets based on their age, set up a cronjob to run the escalation command on a regular basis::
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import socket
|
|||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from time import ctime
|
from time import ctime
|
||||||
|
import traceback
|
||||||
import typing
|
import typing
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@ -56,11 +57,16 @@ STRIPPED_SUBJECT_STRINGS = [
|
|||||||
HTML_EMAIL_ATTACHMENT_FILENAME = _("email_html_body.html")
|
HTML_EMAIL_ATTACHMENT_FILENAME = _("email_html_body.html")
|
||||||
|
|
||||||
|
|
||||||
def process_email(quiet=False):
|
def process_email(quiet: bool = False, debug_to_stdout: bool = False):
|
||||||
|
if debug_to_stdout:
|
||||||
|
print("Extracting email into queues...")
|
||||||
|
q: Queue() # Typing ahead of time for loop to make it more useful in an IDE
|
||||||
for q in Queue.objects.filter(
|
for q in Queue.objects.filter(
|
||||||
email_box_type__isnull=False,
|
email_box_type__isnull=False,
|
||||||
allow_email_submission=True):
|
allow_email_submission=True):
|
||||||
|
log_msg = f"Processing queue: {q.slug} Email address: {q.email_address}..."
|
||||||
|
if debug_to_stdout:
|
||||||
|
print(log_msg)
|
||||||
logger = logging.getLogger('django.helpdesk.queue.' + q.slug)
|
logger = logging.getLogger('django.helpdesk.queue.' + q.slug)
|
||||||
logging_types = {
|
logging_types = {
|
||||||
'info': logging.INFO,
|
'info': logging.INFO,
|
||||||
@ -84,16 +90,24 @@ def process_email(quiet=False):
|
|||||||
logger.addHandler(log_file_handler)
|
logger.addHandler(log_file_handler)
|
||||||
else:
|
else:
|
||||||
log_file_handler = None
|
log_file_handler = None
|
||||||
|
|
||||||
try:
|
|
||||||
if not q.email_box_last_check:
|
if not q.email_box_last_check:
|
||||||
q.email_box_last_check = timezone.now() - timedelta(minutes=30)
|
q.email_box_last_check = timezone.now() - timedelta(minutes=30)
|
||||||
|
try:
|
||||||
queue_time_delta = timedelta(minutes=q.email_box_interval or 0)
|
queue_time_delta = timedelta(minutes=q.email_box_interval or 0)
|
||||||
if (q.email_box_last_check + queue_time_delta) < timezone.now():
|
if (q.email_box_last_check + queue_time_delta) < timezone.now():
|
||||||
process_queue(q, logger=logger)
|
process_queue(q, logger=logger)
|
||||||
q.email_box_last_check = timezone.now()
|
q.email_box_last_check = timezone.now()
|
||||||
q.save()
|
q.save()
|
||||||
|
log_msg: str = f"Queue successfully processed: {q.slug}"
|
||||||
|
logger.info(log_msg)
|
||||||
|
if debug_to_stdout:
|
||||||
|
print(log_msg)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Queue processing failed: {q.slug} -- {e}", exc_info=True)
|
||||||
|
if debug_to_stdout:
|
||||||
|
print(f"Queue processing failed: {q.slug}")
|
||||||
|
print("-"*60)
|
||||||
|
traceback.print_exc(file=sys.stdout)
|
||||||
finally:
|
finally:
|
||||||
# we must close the file handler correctly if it's created
|
# we must close the file handler correctly if it's created
|
||||||
try:
|
try:
|
||||||
@ -106,6 +120,8 @@ def process_email(quiet=False):
|
|||||||
logger.removeHandler(log_file_handler)
|
logger.removeHandler(log_file_handler)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
|
if debug_to_stdout:
|
||||||
|
print("Email extraction into queues completed.")
|
||||||
|
|
||||||
|
|
||||||
def pop3_sync(q, logger, server):
|
def pop3_sync(q, logger, server):
|
||||||
@ -320,7 +336,7 @@ def imap_oauth_sync(q, logger, server):
|
|||||||
|
|
||||||
|
|
||||||
def process_queue(q, logger):
|
def process_queue(q, logger):
|
||||||
logger.info("***** %s: Begin processing mail for django-helpdesk" % ctime())
|
logger.info(f"***** {ctime()}: Begin processing mail for django-helpdesk queue: {q.title}")
|
||||||
|
|
||||||
if q.socks_proxy_type and q.socks_proxy_host and q.socks_proxy_port:
|
if q.socks_proxy_type and q.socks_proxy_host and q.socks_proxy_port:
|
||||||
try:
|
try:
|
||||||
|
@ -30,10 +30,18 @@ class Command(BaseCommand):
|
|||||||
default=False,
|
default=False,
|
||||||
help='Hide details about each queue/message as they are processed',
|
help='Hide details about each queue/message as they are processed',
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--debug_to_stdout',
|
||||||
|
action='store_true',
|
||||||
|
dest='debug_to_stdout',
|
||||||
|
default=False,
|
||||||
|
help='Log additional messaging to stdout.',
|
||||||
|
)
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
quiet = options.get('quiet', False)
|
quiet = options.get('quiet', False)
|
||||||
process_email(quiet=quiet)
|
debug_to_stdout = options.get('debug_to_stdout', False)
|
||||||
|
process_email(quiet=quiet, debug_to_stdout=debug_to_stdout)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -18,6 +18,7 @@ from helpdesk.models import FollowUp, FollowUpAttachment, IgnoreEmail, Queue, Ti
|
|||||||
from helpdesk.tests import utils
|
from helpdesk.tests import utils
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
from mock.mock import patch
|
||||||
from oauthlib.oauth2 import BackendApplicationClient
|
from oauthlib.oauth2 import BackendApplicationClient
|
||||||
import os
|
import os
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
@ -335,12 +336,82 @@ class GetEmailCommonTests(TestCase):
|
|||||||
elif att_retrieved.filename.endswith(email_att_filename):
|
elif att_retrieved.filename.endswith(email_att_filename):
|
||||||
email_attachment_found = True
|
email_attachment_found = True
|
||||||
else:
|
else:
|
||||||
print(f"\n\n%%%%%% {att_retrieved}")
|
|
||||||
self.assertTrue(False, "Unexpected file in ticket attachments: %s" % att_retrieved.filename)
|
self.assertTrue(False, "Unexpected file in ticket attachments: %s" % att_retrieved.filename)
|
||||||
self.assertTrue(email_attachment_found, "Email attachment file not found ticket attachments: %s" % (email_att_filename))
|
self.assertTrue(email_attachment_found, "Email attachment file not found ticket attachments: %s" % (email_att_filename))
|
||||||
self.assertTrue(inline_found, "Inline file not found in email: %s" % (inline_att_filename))
|
self.assertTrue(inline_found, "Inline file not found in email: %s" % (inline_att_filename))
|
||||||
|
|
||||||
|
|
||||||
|
def test_email_with_txt_as_attachment(self):
|
||||||
|
"""
|
||||||
|
Test an email with an txt extension email attachment to the email
|
||||||
|
"""
|
||||||
|
email_message, _, _ = utils.generate_multipart_email(type_list=['plain'])
|
||||||
|
email_att_filename = 'test.txt'
|
||||||
|
file_part = utils.generate_file_mime_part("en_US", email_att_filename, "Testing a simple txt attachment.")
|
||||||
|
email_message.attach(file_part)
|
||||||
|
# Now send the part to the email workflow
|
||||||
|
extract_email_metadata(email_message.as_string(), self.queue_public, self.logger)
|
||||||
|
|
||||||
|
self.assertEqual(len(mail.outbox), 1) # @UndefinedVariable
|
||||||
|
|
||||||
|
ticket = Ticket.objects.get()
|
||||||
|
followup = ticket.followup_set.get()
|
||||||
|
attachments = FollowUpAttachment.objects.filter(followup=followup)
|
||||||
|
self.assertEqual(len(attachments), 1)
|
||||||
|
attachment = attachments[0]
|
||||||
|
self.assertTrue(attachment.filename.endswith(email_att_filename), "The txt file not found in email: %s" % (email_att_filename))
|
||||||
|
|
||||||
|
|
||||||
|
class EmailTaskTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.num_queues = 5
|
||||||
|
self.exception_queues = [2, 4]
|
||||||
|
self.q_ids = []
|
||||||
|
for i in range(self.num_queues):
|
||||||
|
q = Queue.objects.create(
|
||||||
|
title=f'Test{i+1}',
|
||||||
|
slug=f'test{i+1}',
|
||||||
|
email_box_type='local',
|
||||||
|
allow_email_submission=True
|
||||||
|
)
|
||||||
|
self.q_ids.append(q.id)
|
||||||
|
self.logger = logging.getLogger('helpdesk')
|
||||||
|
|
||||||
|
def test_get_email_with_debug_to_stdout_option(self):
|
||||||
|
"""Test debug_to_stdout option """
|
||||||
|
# Test get_email with debug_to_stdout set to True and also False, and verify
|
||||||
|
# handle receives debug_to_stdout option set properly
|
||||||
|
for debug_to_stdout in [True, False]:
|
||||||
|
with mock.patch.object(Command, 'handle', return_value=None) as mocked_handle:
|
||||||
|
call_command('get_email', "--debug_to_stdout") if debug_to_stdout else call_command('get_email')
|
||||||
|
mocked_handle.assert_called_once()
|
||||||
|
for _, kwargs in mocked_handle.call_args_list:
|
||||||
|
self.assertEqual(debug_to_stdout, (kwargs['debug_to_stdout']))
|
||||||
|
|
||||||
|
@patch('helpdesk.email.process_queue')
|
||||||
|
def test_get_email_with_queue_failure(self, mocked_process_queue):
|
||||||
|
"""Test all queues are processed if specified queues have exceptions"""
|
||||||
|
ret_values = [
|
||||||
|
Exception(f"Error Q{i};") if (i in self.exception_queues) else None for i in range(1, self.num_queues+1)
|
||||||
|
]
|
||||||
|
mocked_process_queue.side_effect = ret_values
|
||||||
|
call_command(
|
||||||
|
'get_email',
|
||||||
|
'--debug_to_stdout',
|
||||||
|
)
|
||||||
|
self.assertEqual(mocked_process_queue.call_count, self.num_queues)
|
||||||
|
not_processed_count = Queue.objects.filter(
|
||||||
|
email_box_type__isnull=False,
|
||||||
|
allow_email_submission=True,
|
||||||
|
email_box_last_check__isnull=True).count()
|
||||||
|
self.assertEqual(
|
||||||
|
not_processed_count,
|
||||||
|
len(self.exception_queues),
|
||||||
|
"Incorrect number of queues that did not get processed due to a forced exception."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetEmailParametricTemplate(object):
|
class GetEmailParametricTemplate(object):
|
||||||
"""TestCase that checks basic email functionality across methods and socks configs."""
|
"""TestCase that checks basic email functionality across methods and socks configs."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user