From 42be32b17b0234b959dddd5958c1d3ec90a005f7 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:33:23 +0000 Subject: [PATCH 01/11] Allow to track queue change in follow-ups --- helpdesk/templates/helpdesk/ticket.html | 3 +++ helpdesk/update_ticket.py | 14 ++++++++++++++ helpdesk/views/staff.py | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/helpdesk/templates/helpdesk/ticket.html b/helpdesk/templates/helpdesk/ticket.html index b4eb61db..6efdb8e1 100644 --- a/helpdesk/templates/helpdesk/ticket.html +++ b/helpdesk/templates/helpdesk/ticket.html @@ -150,6 +150,9 @@
+
+
+
{{ form.due_date }}
diff --git a/helpdesk/update_ticket.py b/helpdesk/update_ticket.py index 711566ba..cd2542a3 100644 --- a/helpdesk/update_ticket.py +++ b/helpdesk/update_ticket.py @@ -12,6 +12,7 @@ from helpdesk.decorators import ( is_helpdesk_staff, ) from helpdesk.models import ( + Queue, FollowUp, Ticket, TicketCC, @@ -200,6 +201,7 @@ def update_ticket( owner=-1, ticket_title=None, priority=-1, + queue=-1, new_status=None, time_spent=None, due_date=None, @@ -213,6 +215,8 @@ def update_ticket( title = ticket.title if priority == -1: priority = ticket.priority + if queue == -1: + queue = ticket.queue.id if new_status is None: new_status = ticket.status if new_checklists is None: @@ -302,6 +306,16 @@ def update_ticket( c.save() ticket.priority = priority + if queue != ticket.queue.id: + c = TicketChange( + followup=f, + field=_('Queue'), + old_value=ticket.queue.id, + new_value=queue, + ) + c.save() + ticket.queue.id = queue + if due_date != ticket.due_date: c = TicketChange( followup=f, diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index e1cdc967..8abb9e7b 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -428,6 +428,7 @@ def view_ticket(request, ticket_id): 'form': form, 'active_users': users, 'priorities': Ticket.PRIORITY_CHOICES, + 'queues': queue_choices, 'preset_replies': PreSetReply.objects.filter( Q(queues=ticket.queue) | Q(queues__isnull=True)), 'ticketcc_string': ticketcc_string, @@ -566,6 +567,7 @@ def update_ticket_view(request, ticket_id, public=False): title = request.POST.get('title', '') owner = int(request.POST.get('owner', -1)) priority = int(request.POST.get('priority', ticket.priority)) + queue = int(request.POST.get('queue', ticket.queue.id)) # Check if a change happened on checklists new_checklists = {} @@ -589,6 +591,7 @@ def update_ticket_view(request, ticket_id, public=False): new_status == ticket.status, title == ticket.title, priority == int(ticket.priority), + queue == int(ticket.queue.id), due_date == ticket.due_date, (owner == -1) or (not owner and not ticket.assigned_to) or (owner and User.objects.get(id=owner) == ticket.assigned_to), @@ -605,6 +608,7 @@ def update_ticket_view(request, ticket_id, public=False): public = request.POST.get('public', False), owner = int(request.POST.get('owner', -1)), priority = int(request.POST.get('priority', -1)), + queue = int(request.POST.get('queue', -1)), new_status = new_status, time_spent = get_time_spent_from_request(request), due_date = get_due_date_from_request_or_ticket(request, ticket), From bb4e05ba39abc533ef2de4764842e6e7280789c0 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:43:11 +0000 Subject: [PATCH 02/11] Removed unused Queue model import --- helpdesk/update_ticket.py | 1 - 1 file changed, 1 deletion(-) diff --git a/helpdesk/update_ticket.py b/helpdesk/update_ticket.py index cd2542a3..a1746398 100644 --- a/helpdesk/update_ticket.py +++ b/helpdesk/update_ticket.py @@ -12,7 +12,6 @@ from helpdesk.decorators import ( is_helpdesk_staff, ) from helpdesk.models import ( - Queue, FollowUp, Ticket, TicketCC, From e526c21aeff5c6c84411af1c5ec8b40a197c81f5 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:05:32 +0000 Subject: [PATCH 03/11] Update ticket queue foreign key id --- helpdesk/update_ticket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/update_ticket.py b/helpdesk/update_ticket.py index a1746398..5a2e02e1 100644 --- a/helpdesk/update_ticket.py +++ b/helpdesk/update_ticket.py @@ -313,7 +313,7 @@ def update_ticket( new_value=queue, ) c.save() - ticket.queue.id = queue + ticket.queue_id = queue if due_date != ticket.due_date: c = TicketChange( From e372f4447b19b5ebc62be0081c375d7a609bae5a Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:18:43 +0000 Subject: [PATCH 04/11] Adding test_update_ticket_queue test --- helpdesk/tests/test_ticket_actions.py | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/helpdesk/tests/test_ticket_actions.py b/helpdesk/tests/test_ticket_actions.py index abf95c3f..0daf1f1d 100644 --- a/helpdesk/tests/test_ticket_actions.py +++ b/helpdesk/tests/test_ticket_actions.py @@ -7,6 +7,7 @@ from helpdesk import settings as helpdesk_settings from helpdesk.models import CustomField, Queue, Ticket from helpdesk.templatetags.ticket_to_link import num_to_link from helpdesk.user import HelpdeskUser +from django.utils.translation import gettext_lazy as _ try: # python 3 @@ -323,3 +324,46 @@ class TicketActionsTestCase(TestCase): ticket_1_follow_up, ticket_2_follow_up]) self.assertEqual(list(ticket_1.ticketcc_set.all()), [ticket_1_cc, ticket_2_cc]) + + def test_update_ticket_queue(self): + """Tests whether user can change the queue in the Respond to this ticket section.""" + + # log user in + self.loginUser() + + # create ticket + initial_data = { + 'title': 'Queue change ticket test', + 'queue': self.queue_public, + 'assigned_to': self.user, + 'status': Ticket.OPEN_STATUS, + } + ticket = Ticket.objects.create(**initial_data) + ticket_id = ticket.id + + # initial queue + self.assertEqual(ticket.queue, self.queue_public) + + # POST first follow-up with new queue + new_queue = Queue.objects.create( + title='New Queue', + slug='newqueue', + ) + post_data = { + 'comment': 'first follow-up in new queue', + 'queue': str(new_queue.id), + } + response = self.client.post(reverse('helpdesk:update', + kwargs={'ticket_id': ticket_id}), + post_data) + + # queue was correctly modified + ticket.refresh_from_db() + self.assertEqual(ticket.queue, new_queue) + + # ticket change was saved + latest_fup = ticket.followup_set.latest('date') + latest_ticketchange = latest_fup.ticketchange_set.latest('id') + self.assertEqual(latest_ticketchange.field, _('Queue')) + self.assertEqual(int(latest_ticketchange.old_value), self.queue_public.id) + self.assertEqual(int(latest_ticketchange.new_value), new_queue.id) \ No newline at end of file From f4ab7a5226ea26495630cf249b11c6be085671eb Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:25:35 +0000 Subject: [PATCH 05/11] Refactored time_spent calculation queue exclusions to reflect queue changes over time --- helpdesk/models.py | 67 +++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 1781093f..a9ce9a1b 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1006,36 +1006,61 @@ class FollowUp(models.Model): def time_spent_calculation(self): "Returns timedelta according to rules settings." + + open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS + holidays = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS + exclude_statuses = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES + exclude_queues = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES + + # queryset for this ticket previous follow-ups + prev_fup_qs = self.ticket.followup_set.all() + if self.id: + # if the follow-up exist in DB, only keep previous follow-ups + prev_fup_qs = prev_fup_qs.filter(date__lt=self.date) + + # handle exclusions + + # extract previous status from follow-up or ticket for exclusion check + if exclude_statuses: + try: + prev_fup = prev_fup_qs.latest("date") + prev_status = prev_fup.new_status + except ObjectDoesNotExist: + prev_status = self.ticket.status + + # don't calculate status exclusions + if prev_status in exclude_statuses: + return datetime.timedelta(seconds=0) + + # find the previous queue for exclusion check + if exclude_queues: + try: + prev_fup_ids = prev_fup_qs.values_list('id', flat=True) + prev_queue_change = TicketChange.objects.filter(followup_id__in=prev_fup_ids, + field=_('Queue')).latest('id') + prev_queue = Queue.objects.get(pk=prev_queue_change.new_value) + prev_queue_slug = prev_queue.slug + except ObjectDoesNotExist: + prev_queue_slug = self.ticket.queue.slug + + # don't calculate queue exclusions + if prev_queue_slug in exclude_queues: + return datetime.timedelta(seconds=0) + + # no exclusion found + + time_spent_seconds = 0 # extract earliest from previous follow-up or ticket try: - prev_fup_qs = self.ticket.followup_set.all() - if self.id: - prev_fup_qs = prev_fup_qs.filter(id__lt=self.id) prev_fup = prev_fup_qs.latest("date") earliest = prev_fup.date except ObjectDoesNotExist: earliest = self.ticket.created - # extract previous status from follow-up or ticket - try: - prev_fup_qs = self.ticket.followup_set.exclude(new_status__isnull=True) - if self.id: - prev_fup_qs = prev_fup_qs.filter(id__lt=self.id) - prev_fup = prev_fup_qs.latest("date") - prev_status = prev_fup.new_status - except ObjectDoesNotExist: - prev_status = self.ticket.status - # latest time is current follow-up date latest = self.date - time_spent_seconds = 0 - open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS - holidays = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS - exclude_statuses = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES - exclude_queues = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES - # split time interval by days days = latest.toordinal() - earliest.toordinal() for day in range(days + 1): @@ -1054,9 +1079,7 @@ class FollowUp(models.Model): start_day_time = middle_day_time.replace(hour=0, minute=0, second=0) end_day_time = middle_day_time.replace(hour=23, minute=59, second=59, microsecond=999999) - if (start_day_time.strftime("%Y-%m-%d") not in holidays and - prev_status not in exclude_statuses and - self.ticket.queue.slug not in exclude_queues): + if start_day_time.strftime("%Y-%m-%d") not in holidays: time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) return datetime.timedelta(seconds=time_spent_seconds) From 6e845f6351e9adce9a07137fdb82089cc5a16496 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:27:42 +0000 Subject: [PATCH 06/11] Adding test for multiple exclusion queues through follow-ups --- helpdesk/tests/test_time_spent_auto.py | 70 +++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/helpdesk/tests/test_time_spent_auto.py b/helpdesk/tests/test_time_spent_auto.py index 2cdaab91..ac3a5a0b 100644 --- a/helpdesk/tests/test_time_spent_auto.py +++ b/helpdesk/tests/test_time_spent_auto.py @@ -2,7 +2,10 @@ from datetime import datetime, timedelta from django.contrib.auth.hashers import make_password from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.test import TestCase, override_settings +from django.test.client import Client +from django.urls import reverse from helpdesk.models import FollowUp, Queue, Ticket from helpdesk import settings as helpdesk_settings import uuid @@ -33,6 +36,21 @@ class TimeSpentAutoTestCase(TestCase): is_active=True ) + self.client = Client() + + + def loginUser(self, is_staff=True): + """Create a staff user and login""" + User = get_user_model() + self.user = User.objects.create( + username='User_1', + is_staff=is_staff, + ) + self.user.set_password('pass') + self.user.save() + self.client.login(username='User_1', password='pass') + + def test_add_two_followups_time_spent_auto(self): """Tests automatic time_spent calculation.""" # activate automatic calculation @@ -251,4 +269,54 @@ class TimeSpentAutoTestCase(TestCase): self.assertEqual(ticket.time_spent.total_seconds(), 0.0) # Remove queues exclusion - helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () \ No newline at end of file + helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () + + def test_http_followup_time_spent_auto_exclude_queues(self): + """Tests automatic time_spent calculation queues exclusion with client""" + + # activate automatic calculation + helpdesk_settings.FOLLOWUP_TIME_SPENT_AUTO = True + helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = ('stop1', 'stop2') + + # make staff user + self.loginUser() + + # create queues + queues_sequence = ('new', 'stop1', 'resume1', 'stop2', 'resume2', 'end') + queues = dict() + for slug in queues_sequence: + queues[slug] = Queue.objects.create( + title=slug, + slug=slug, + ) + + # create ticket + initial_data = { + 'title': 'Queue change ticket test', + 'queue': queues['new'], + 'assigned_to': self.user, + 'status': Ticket.OPEN_STATUS, + 'created': datetime.strptime('2024-04-09T08:00:00+00:00', "%Y-%m-%dT%H:%M:%S%z") + } + ticket = Ticket.objects.create(**initial_data) + + # create a change queue follow-up every hour + # first follow-up created at the same time of the ticket without queue change + # new --1h--> stop1 --0h--> resume1 --1h--> stop2 --0h--> --1h--> end + for (i, queue) in enumerate(queues_sequence): + # create follow-up + post_data = { + 'comment': 'ticket in queue {}'.format(queue), + 'queue': queues[queue].id, + } + response = self.client.post(reverse('helpdesk:update', kwargs={ + 'ticket_id': ticket.id}), post_data) + latest_fup = ticket.followup_set.latest('id') + latest_fup.date = ticket.created + timedelta(minutes=60 * i) + latest_fup.time_spent = None + latest_fup.save() + + # total ticket time for followups is 5 hours + self.assertEqual(latest_fup.date - ticket.created, timedelta(hours=5)) + # calculated time spent with 2 hours exclusion is 3 hours + self.assertEqual(ticket.time_spent.total_seconds(), 3 * 3600.0) \ No newline at end of file From d1af580483b1c190df912b969fafaa5c24743bde Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:32:11 +0000 Subject: [PATCH 07/11] Remove queues exclusion at the end of test --- helpdesk/tests/test_time_spent_auto.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/helpdesk/tests/test_time_spent_auto.py b/helpdesk/tests/test_time_spent_auto.py index ac3a5a0b..792a3843 100644 --- a/helpdesk/tests/test_time_spent_auto.py +++ b/helpdesk/tests/test_time_spent_auto.py @@ -319,4 +319,7 @@ class TimeSpentAutoTestCase(TestCase): # total ticket time for followups is 5 hours self.assertEqual(latest_fup.date - ticket.created, timedelta(hours=5)) # calculated time spent with 2 hours exclusion is 3 hours - self.assertEqual(ticket.time_spent.total_seconds(), 3 * 3600.0) \ No newline at end of file + self.assertEqual(ticket.time_spent.total_seconds(), 3 * 3600.0) + + # remove queues exclusion + helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () \ No newline at end of file From aa5e2d0c67aa64f9887aea945fc6d19edee0c703 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:33:20 +0000 Subject: [PATCH 08/11] Fixed test comment --- helpdesk/tests/test_time_spent_auto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/tests/test_time_spent_auto.py b/helpdesk/tests/test_time_spent_auto.py index 792a3843..a7ea0bbf 100644 --- a/helpdesk/tests/test_time_spent_auto.py +++ b/helpdesk/tests/test_time_spent_auto.py @@ -302,7 +302,7 @@ class TimeSpentAutoTestCase(TestCase): # create a change queue follow-up every hour # first follow-up created at the same time of the ticket without queue change - # new --1h--> stop1 --0h--> resume1 --1h--> stop2 --0h--> --1h--> end + # new --1h--> stop1 --0h--> resume1 --1h--> stop2 --0h--> resume2 --1h--> end for (i, queue) in enumerate(queues_sequence): # create follow-up post_data = { From 62ef86a04732f4d971d8d6ba5ec6addf8c4f0e58 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:45:41 +0000 Subject: [PATCH 09/11] Settings assertion values in hours --- helpdesk/tests/test_time_spent_auto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpdesk/tests/test_time_spent_auto.py b/helpdesk/tests/test_time_spent_auto.py index a7ea0bbf..56b25ad0 100644 --- a/helpdesk/tests/test_time_spent_auto.py +++ b/helpdesk/tests/test_time_spent_auto.py @@ -312,14 +312,14 @@ class TimeSpentAutoTestCase(TestCase): response = self.client.post(reverse('helpdesk:update', kwargs={ 'ticket_id': ticket.id}), post_data) latest_fup = ticket.followup_set.latest('id') - latest_fup.date = ticket.created + timedelta(minutes=60 * i) + latest_fup.date = ticket.created + timedelta(hours=i) latest_fup.time_spent = None latest_fup.save() # total ticket time for followups is 5 hours self.assertEqual(latest_fup.date - ticket.created, timedelta(hours=5)) # calculated time spent with 2 hours exclusion is 3 hours - self.assertEqual(ticket.time_spent.total_seconds(), 3 * 3600.0) + self.assertEqual(ticket.time_spent.total_seconds(), timedelta(hours=3).total_seconds()) # remove queues exclusion helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () \ No newline at end of file From 5b39c9aeeb0a6c1246199e499bd67f914964fcb3 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:39:58 +0200 Subject: [PATCH 10/11] Update forloop for queues with explicit variable names in ticket template Co-authored-by: Benbb96 --- helpdesk/templates/helpdesk/ticket.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/templates/helpdesk/ticket.html b/helpdesk/templates/helpdesk/ticket.html index 6efdb8e1..8f0ede76 100644 --- a/helpdesk/templates/helpdesk/ticket.html +++ b/helpdesk/templates/helpdesk/ticket.html @@ -151,7 +151,7 @@
-
+
{{ form.due_date }}
From a775622521f7cda25107c03772fe74fcc13d4e68 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:44:56 +0200 Subject: [PATCH 11/11] Create ticket change through instance relationship Co-authored-by: Benbb96 --- helpdesk/update_ticket.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/helpdesk/update_ticket.py b/helpdesk/update_ticket.py index 5a2e02e1..deca0b40 100644 --- a/helpdesk/update_ticket.py +++ b/helpdesk/update_ticket.py @@ -306,13 +306,11 @@ def update_ticket( ticket.priority = priority if queue != ticket.queue.id: - c = TicketChange( - followup=f, + c = f.ticketchange_set.create( field=_('Queue'), old_value=ticket.queue.id, new_value=queue, ) - c.save() ticket.queue_id = queue if due_date != ticket.due_date: