forked from extern/django-helpdesk
new option 'HELPDESK_DASHBOARD_BASIC_TICKET_STATS'
- shows quick ticket stats in dashboard - links to detailed 'days until ticket is closed by month' plot
This commit is contained in:
parent
b4fe8bd91f
commit
0a8f4ce6d6
@ -118,6 +118,8 @@ HELPDESK_DASHBOARD_SHOW_DELETE_UNASSIGNED = getattr(settings, 'HELPDESK_DASHBOAR
|
|||||||
# hide empty queues in dashboard overview?
|
# hide empty queues in dashboard overview?
|
||||||
HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES = getattr(settings, 'HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES', True)
|
HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES = getattr(settings, 'HELPDESK_DASHBOARD_HIDE_EMPTY_QUEUES', True)
|
||||||
|
|
||||||
|
# show basic ticket stats on dashboard?
|
||||||
|
HELPDESK_DASHBOARD_BASIC_TICKET_STATS = getattr(settings, 'HELPDESK_DASHBOARD_BASIC_TICKET_STATS', False)
|
||||||
|
|
||||||
|
|
||||||
''' options for footer '''
|
''' options for footer '''
|
||||||
|
@ -29,6 +29,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% if helpdesk_settings.HELPDESK_DASHBOARD_BASIC_TICKET_STATS %}
|
||||||
|
<br style='clear: both;' />
|
||||||
|
<br style='clear: both;' />
|
||||||
|
|
||||||
|
<table width='100%'>
|
||||||
|
|
||||||
|
<tr class='row_tablehead' style="color: #fbff00;"><td colspan='2'><i>{% trans "Current Ticket Stats" %}</i></td></tr>
|
||||||
|
<tr><td colspan='2'>- {% trans "Current average number of days until ticket is closed: " %}<strong style="color: red;">{{ basic_ticket_stats.average_nbr_days_until_ticket_closed }}</strong>.
|
||||||
|
{% trans "Click" %} <strong><a href="{% url helpdesk_report_index %}daysuntilticketclosedbymonth">here</a></strong> {% trans "for detailed average by month." %} </td></tr>
|
||||||
|
<tr><td colspan='2'>- {% trans "Distribution of open tickets, grouped by time period:" %}</td></tr>
|
||||||
|
<tr class='row_columnheads'><th>{% trans "Days since opened" %}</th><th>{% trans "Number of open tickets" %}</th></tr>
|
||||||
|
|
||||||
|
{% for entry in basic_ticket_stats.open_ticket_stats %}
|
||||||
|
<tr class='row_{% cycle odd,even %} row_hover'>
|
||||||
|
<th style="padding-left: 20px;">{{ entry.0 }}</th>
|
||||||
|
<td style="padding-left: 20px;"><span style="color: {{ entry.2 }};">{% if entry.1 > 0 %}<a href="{% url helpdesk_list %}?{{ entry.3 }}">{{ entry.1 }}</a>{% else %}{{ entry.1 }}{% endif %}</span></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if all_tickets_reported_by_current_user %}
|
{% if all_tickets_reported_by_current_user %}
|
||||||
<br style='clear: both;' />
|
<br style='clear: both;' />
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<li><a href='queuepriority/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Priority" %}</a></li>
|
<li><a href='queuepriority/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Priority" %}</a></li>
|
||||||
<li><a href='queuestatus/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Status" %}</a></li>
|
<li><a href='queuestatus/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Status" %}</a></li>
|
||||||
<li><a href='queuemonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
|
<li><a href='queuemonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
|
||||||
|
<li><a href='daysuntilticketclosedbymonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "Days until ticket closed by Month" %}</a></li>
|
||||||
|
|
||||||
</ul></li>
|
</ul></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -8,6 +8,7 @@ views/staff.py - The bulk of the application - provides most business logic and
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -78,6 +79,11 @@ def dashboard(request):
|
|||||||
).order_by('status')
|
).order_by('status')
|
||||||
|
|
||||||
|
|
||||||
|
# calculate basic ticket stats if requested
|
||||||
|
basic_ticket_stats = False
|
||||||
|
if helpdesk_settings.HELPDESK_DASHBOARD_BASIC_TICKET_STATS:
|
||||||
|
basic_ticket_stats = calc_basic_ticket_stats(Ticket)
|
||||||
|
|
||||||
# The following query builds a grid of queues & ticket statuses,
|
# The following query builds a grid of queues & ticket statuses,
|
||||||
# to be displayed to the user. EG:
|
# to be displayed to the user. EG:
|
||||||
# Open Resolved
|
# Open Resolved
|
||||||
@ -122,6 +128,7 @@ def dashboard(request):
|
|||||||
'unassigned_tickets': unassigned_tickets,
|
'unassigned_tickets': unassigned_tickets,
|
||||||
'all_tickets_reported_by_current_user': all_tickets_reported_by_current_user,
|
'all_tickets_reported_by_current_user': all_tickets_reported_by_current_user,
|
||||||
'dash_tickets': dash_tickets,
|
'dash_tickets': dash_tickets,
|
||||||
|
'basic_ticket_stats': basic_ticket_stats,
|
||||||
}))
|
}))
|
||||||
dashboard = staff_member_required(dashboard)
|
dashboard = staff_member_required(dashboard)
|
||||||
|
|
||||||
@ -934,7 +941,7 @@ report_index = staff_member_required(report_index)
|
|||||||
|
|
||||||
|
|
||||||
def run_report(request, report):
|
def run_report(request, report):
|
||||||
if Ticket.objects.all().count() == 0 or report not in ('queuemonth', 'usermonth', 'queuestatus', 'queuepriority', 'userstatus', 'userpriority', 'userqueue'):
|
if Ticket.objects.all().count() == 0 or report not in ('queuemonth', 'usermonth', 'queuestatus', 'queuepriority', 'userstatus', 'userpriority', 'userqueue', 'daysuntilticketclosedbymonth'):
|
||||||
return HttpResponseRedirect(reverse("helpdesk_report_index"))
|
return HttpResponseRedirect(reverse("helpdesk_report_index"))
|
||||||
|
|
||||||
report_queryset = Ticket.objects.all().select_related()
|
report_queryset = Ticket.objects.all().select_related()
|
||||||
@ -958,6 +965,8 @@ def run_report(request, report):
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
summarytable = defaultdict(int)
|
summarytable = defaultdict(int)
|
||||||
|
# a second table for more complex queries
|
||||||
|
summarytable2 = defaultdict(int)
|
||||||
|
|
||||||
months = (
|
months = (
|
||||||
_('Jan'),
|
_('Jan'),
|
||||||
@ -1038,8 +1047,14 @@ def run_report(request, report):
|
|||||||
possible_options = periods
|
possible_options = periods
|
||||||
charttype = 'date'
|
charttype = 'date'
|
||||||
|
|
||||||
|
elif report == 'daysuntilticketclosedbymonth':
|
||||||
|
title = _('Days until ticket closed by Month')
|
||||||
|
col1heading = _('Queue')
|
||||||
|
possible_options = periods
|
||||||
|
charttype = 'date'
|
||||||
|
|
||||||
|
|
||||||
|
metric3 = False
|
||||||
for ticket in report_queryset:
|
for ticket in report_queryset:
|
||||||
if report == 'userpriority':
|
if report == 'userpriority':
|
||||||
metric1 = u'%s' % ticket.get_assigned_to
|
metric1 = u'%s' % ticket.get_assigned_to
|
||||||
@ -1069,10 +1084,23 @@ def run_report(request, report):
|
|||||||
metric1 = u'%s' % ticket.queue.title
|
metric1 = u'%s' % ticket.queue.title
|
||||||
metric2 = u'%s %s' % (months[ticket.created.month - 1], ticket.created.year)
|
metric2 = u'%s %s' % (months[ticket.created.month - 1], ticket.created.year)
|
||||||
|
|
||||||
|
elif report == 'daysuntilticketclosedbymonth':
|
||||||
|
metric1 = u'%s' % ticket.queue.title
|
||||||
|
metric2 = u'%s %s' % (months[ticket.created.month - 1], ticket.created.year)
|
||||||
|
metric3 = ticket.modified - ticket.created
|
||||||
|
metric3 = metric3.days
|
||||||
|
|
||||||
summarytable[metric1, metric2] += 1
|
summarytable[metric1, metric2] += 1
|
||||||
|
if metric3:
|
||||||
|
if report == 'daysuntilticketclosedbymonth':
|
||||||
|
summarytable2[metric1, metric2] += metric3
|
||||||
|
|
||||||
table = []
|
table = []
|
||||||
|
|
||||||
|
if report == 'daysuntilticketclosedbymonth':
|
||||||
|
for key in summarytable2.keys():
|
||||||
|
summarytable[key] = summarytable2[key] / summarytable[key]
|
||||||
|
|
||||||
header1 = sorted(set(list( i.encode('utf-8') for i,_ in summarytable.keys() )))
|
header1 = sorted(set(list( i.encode('utf-8') for i,_ in summarytable.keys() )))
|
||||||
|
|
||||||
column_headings = [col1heading] + possible_options
|
column_headings = [col1heading] + possible_options
|
||||||
@ -1262,3 +1290,76 @@ def attachment_del(request, ticket_id, attachment_id):
|
|||||||
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id]))
|
return HttpResponseRedirect(reverse('helpdesk_view', args=[ticket_id]))
|
||||||
attachment_del = staff_member_required(attachment_del)
|
attachment_del = staff_member_required(attachment_del)
|
||||||
|
|
||||||
|
def calc_average_nbr_days_until_ticket_resolved(Tickets):
|
||||||
|
nbr_closed_tickets = len(Tickets)
|
||||||
|
days_per_ticket = 0
|
||||||
|
days_each_ticket = list()
|
||||||
|
|
||||||
|
for ticket in Tickets:
|
||||||
|
time_ticket_open = ticket.modified - ticket.created
|
||||||
|
days_this_ticket = time_ticket_open.days
|
||||||
|
days_per_ticket += days_this_ticket
|
||||||
|
days_each_ticket.append(days_this_ticket)
|
||||||
|
|
||||||
|
mean_per_ticket = days_per_ticket / nbr_closed_tickets
|
||||||
|
return mean_per_ticket
|
||||||
|
|
||||||
|
def calc_basic_ticket_stats(Ticket):
|
||||||
|
# all closed tickets - independent of user
|
||||||
|
all_closed_tickets = Ticket.objects.filter(status = Ticket.CLOSED_STATUS)
|
||||||
|
average_nbr_days_until_ticket_closed = calc_average_nbr_days_until_ticket_resolved(all_closed_tickets)
|
||||||
|
|
||||||
|
# all not closed tickets (open, reopened, resolved,) - independent of user
|
||||||
|
all_open_tickets = Ticket.objects.exclude(status = Ticket.CLOSED_STATUS)
|
||||||
|
today = datetime.today()
|
||||||
|
|
||||||
|
date_30 = date_rel_to_today(today, 30)
|
||||||
|
date_60 = date_rel_to_today(today, 60)
|
||||||
|
date_30_str = date_30.strftime('%Y-%m-%d')
|
||||||
|
date_60_str = date_60.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
|
# < 30
|
||||||
|
ota_le_30 = all_open_tickets.filter(created__gt = date_30)
|
||||||
|
N_ota_le_30 = len(ota_le_30)
|
||||||
|
|
||||||
|
# > 30 & < 60
|
||||||
|
ota_le_60_ge_30 = all_open_tickets.filter(created__gte = date_60, created__lte = date_30)
|
||||||
|
N_ota_le_60_ge_30 = len(ota_le_60_ge_30)
|
||||||
|
|
||||||
|
# > 60
|
||||||
|
ota_ge_60 = all_open_tickets.filter(created__lt = date_60)
|
||||||
|
N_ota_ge_60 = len(ota_ge_60)
|
||||||
|
|
||||||
|
# (O)pen (T)icket (S)tats
|
||||||
|
ots = list()
|
||||||
|
# label, number entries, color, sort_string
|
||||||
|
ots.append(['< 30 days', N_ota_le_30, get_color_for_nbr_days(N_ota_le_30), sort_string(date_30_str, ''), ])
|
||||||
|
ots.append(['30 - 60 days', N_ota_le_60_ge_30, get_color_for_nbr_days(N_ota_le_60_ge_30), sort_string(date_60_str, date_30_str), ])
|
||||||
|
ots.append(['> 60 days', N_ota_ge_60, get_color_for_nbr_days(N_ota_ge_60), sort_string('', date_60_str), ])
|
||||||
|
|
||||||
|
# put together basic stats
|
||||||
|
basic_ticket_stats = { 'average_nbr_days_until_ticket_closed': average_nbr_days_until_ticket_closed,
|
||||||
|
'open_ticket_stats': ots, }
|
||||||
|
|
||||||
|
return basic_ticket_stats
|
||||||
|
|
||||||
|
def get_color_for_nbr_days(nbr_days):
|
||||||
|
''' '''
|
||||||
|
if nbr_days < 5:
|
||||||
|
color_string = 'green'
|
||||||
|
elif nbr_days >= 5 and nbr_days < 10:
|
||||||
|
color_string = 'orange'
|
||||||
|
else: # more than 10 days
|
||||||
|
color_string = 'red'
|
||||||
|
|
||||||
|
return color_string
|
||||||
|
|
||||||
|
def days_since_created(today, ticket):
|
||||||
|
return (today - ticket.created).days
|
||||||
|
|
||||||
|
def date_rel_to_today(today, offset):
|
||||||
|
return today - timedelta(days = offset)
|
||||||
|
|
||||||
|
def sort_string(begin, end):
|
||||||
|
return 'sort=created&date_from=%s&date_to=%s&status=%s&status=%s&status=%s' %(begin, end, Ticket.OPEN_STATUS, Ticket.REOPENED_STATUS, Ticket.RESOLVED_STATUS)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user