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?
|
||||
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 '''
|
||||
|
@ -29,6 +29,29 @@
|
||||
</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 %}
|
||||
<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='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='daysuntilticketclosedbymonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "Days until ticket closed by Month" %}</a></li>
|
||||
|
||||
</ul></li>
|
||||
</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 timedelta
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
@ -78,6 +79,11 @@ def dashboard(request):
|
||||
).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,
|
||||
# to be displayed to the user. EG:
|
||||
# Open Resolved
|
||||
@ -122,6 +128,7 @@ def dashboard(request):
|
||||
'unassigned_tickets': unassigned_tickets,
|
||||
'all_tickets_reported_by_current_user': all_tickets_reported_by_current_user,
|
||||
'dash_tickets': dash_tickets,
|
||||
'basic_ticket_stats': basic_ticket_stats,
|
||||
}))
|
||||
dashboard = staff_member_required(dashboard)
|
||||
|
||||
@ -934,7 +941,7 @@ report_index = staff_member_required(report_index)
|
||||
|
||||
|
||||
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"))
|
||||
|
||||
report_queryset = Ticket.objects.all().select_related()
|
||||
@ -958,6 +965,8 @@ def run_report(request, report):
|
||||
|
||||
from collections import defaultdict
|
||||
summarytable = defaultdict(int)
|
||||
# a second table for more complex queries
|
||||
summarytable2 = defaultdict(int)
|
||||
|
||||
months = (
|
||||
_('Jan'),
|
||||
@ -1038,8 +1047,14 @@ def run_report(request, report):
|
||||
possible_options = periods
|
||||
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:
|
||||
if report == 'userpriority':
|
||||
metric1 = u'%s' % ticket.get_assigned_to
|
||||
@ -1069,10 +1084,23 @@ def run_report(request, report):
|
||||
metric1 = u'%s' % ticket.queue.title
|
||||
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
|
||||
if metric3:
|
||||
if report == 'daysuntilticketclosedbymonth':
|
||||
summarytable2[metric1, metric2] += metric3
|
||||
|
||||
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() )))
|
||||
|
||||
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]))
|
||||
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