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:
Andreas Kotowicz 2012-03-12 14:39:27 +01:00
parent b4fe8bd91f
commit 0a8f4ce6d6
4 changed files with 128 additions and 1 deletions

View File

@ -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 '''

View File

@ -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;' />

View File

@ -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>

View File

@ -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)