Overhaul reporting: remove custom SQL commands for compatibility reasons. Code is MUCh simpler now. Also allows user to specify a saved search query via GET paramater - need to add links to this, as it lets them filter charts - very useful\!

This commit is contained in:
Ross Poulton 2011-05-11 20:07:46 +10:00
parent eea960f9b9
commit ac95f9d893
3 changed files with 130 additions and 231 deletions

View File

@ -119,104 +119,6 @@ def send_templated_mail(template_name, email_context, recipients, sender=None, b
return msg.send(fail_silently) return msg.send(fail_silently)
def normalise_data(data, to=100):
"""
Used for normalising data prior to graphing with Google charting API. EG:
[1, 4, 10] becomes [10, 40, 100]
[36, 54, 240] becomes [15, 23, 100]
"""
max_value = max(data)
if max_value > to:
new_data = []
for d in data:
new_data.append(int(d/float(max_value)*to))
data = new_data
return data
def line_chart(data):
"""
'data' is a list of lists making a table.
Row 1, columns 2-n are data headings (the time periods)
Rows 2-n are data, with column 1 being the line labels
"""
column_headings = data[0][1:]
max = 0
for row in data[1:]:
for field in row[1:]:
if field > max:
max = field
# Set width to '65px * number of months + 100 for headings.'.
chart_url = 'http://chart.apis.google.com/chart?cht=lc&chs=%sx150&chd=t:' % (min(len(column_headings)*65+100, 1000))
first_row = True
row_headings = []
for row in data[1:]:
# Add data to URL, normalised to the maximum for all lines on this chart
norm = normalise_data(row[1:], max)
if not first_row:
chart_url += '|'
chart_url += ','.join([str(num) for num in norm])
row_headings.append(row[0])
first_row = False
chart_url += '&chds='
rows = len(data)-1
first = True
for row in range(rows):
# Set maximum data ranges to '0:x' where 'x' is the maximum number in use.
if not first:
chart_url += ','
chart_url += '0,%s' % max
first = False
chart_url += '&chdl=%s' % '|'.join(row_headings) # Display legend/labels
chart_url += '&chco=%s' % ','.join(chart_colours) # Default colour set
chart_url += '&chxt=x,y' # Turn on axis labels
chart_url += '&chxl=0:|%s|1:|0|%s' % ('|'.join(column_headings), max) # Axis Label Text
return chart_url
def bar_chart(data):
"""
'data' is a list of lists making a table.
Row 1, columns 2-n are data headings
Rows 2-n are data, with column 1 being the line labels
"""
column_headings = data[0][1:]
max = 0
for row in data[1:]:
for field in row[1:]:
if field > max:
max = field
# Set width to '220px * number of months'.
chart_url = 'http://chart.apis.google.com/chart?cht=bvg&chs=%sx150&chd=t:' % (min(len(column_headings) * 220, 1000))
first_row = True
row_headings = []
for row in data[1:]:
# Add data to URL, normalised to the maximum for all lines on this chart
norm = normalise_data(row[1:], max)
if not first_row:
chart_url += '|'
chart_url += ','.join([str(num) for num in norm])
row_headings.append(row[0])
first_row = False
chart_url += '&chds=0,%s' % max
chart_url += '&chdl=%s' % '|'.join(row_headings) # Display legend/labels
chart_url += '&chco=%s' % ','.join(chart_colours) # Default colour set
chart_url += '&chxt=x,y' # Turn on axis labels
chart_url += '&chxl=0:|%s|1:|0|%s' % ('|'.join(column_headings), max) # Axis Label Text
return chart_url
def query_to_dict(results, descriptions): def query_to_dict(results, descriptions):
""" """
Replacement method for cursor.dictfetchall() as that method no longer Replacement method for cursor.dictfetchall() as that method no longer

View File

@ -3,33 +3,29 @@
{% block helpdesk_title %}{% trans "Reports & Statistics" %}{% endblock %} {% block helpdesk_title %}{% trans "Reports & Statistics" %}{% endblock %}
{% block helpdesk_body %} {% block helpdesk_body %}
{% ifequal number_tickets 0 %}{% blocktrans %} <h2>{% trans "Reports &amp; Statistics" %}</h2>
<h2>Reports &amp; Statistics</h2>
<p>You haven't created any tickets yet, so you cannot run any reports.</p> {% ifequal number_tickets 0 %}
{% endblocktrans %} <p>{% trans "You haven't created any tickets yet, so you cannot run any reports." %}</p>
{% else %} {% else %}
{% blocktrans %}
<h2>Reports &amp; Statistics</h2>
<ul> <ul>
<li>User<ul> <li>{% trans "Reports By User" %}<ul>
<li><a href='userpriority/'>by Priority</a></li> <li><a href='userpriority/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Priority" %}</a></li>
<li><a href='userqueue/'>by Queue</a></li> <li><a href='userqueue/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Queue" %}</a></li>
<li><a href='userstatus/'>by Status</a></li> <li><a href='userstatus/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Status" %}</a></li>
<li><a href='usermonth/'>by Month</a></li> <li><a href='usermonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
</ul></li> </ul></li>
<li>Queue<ul> <li>{% trans "Reports By Queue" %}<ul>
<li><a href='queuepriority/'>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/'>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/'>by Month</a></li> <li><a href='queuemonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
</ul></li> </ul></li>
</ul> </ul>
{% endblocktrans %}{% endifequal %}{% endblock %} {% endifequal %}{% endblock %}

View File

@ -24,7 +24,7 @@ from django.utils.translation import ugettext as _
from django.utils.html import escape from django.utils.html import escape
from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm, TicketDependencyForm from helpdesk.forms import TicketForm, UserSettingsForm, EmailIgnoreForm, EditTicketForm, TicketCCForm, EditFollowUpForm, TicketDependencyForm
from helpdesk.lib import send_templated_mail, line_chart, bar_chart, query_to_dict, apply_query, safe_template_context from helpdesk.lib import send_templated_mail, query_to_dict, apply_query, safe_template_context
from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency from helpdesk.models import Ticket, Queue, FollowUp, TicketChange, PreSetReply, Attachment, SavedSearch, IgnoreEmail, TicketCC, TicketDependency
from helpdesk.settings import HAS_TAG_SUPPORT from helpdesk.settings import HAS_TAG_SUPPORT
@ -745,54 +745,56 @@ rss_list = staff_member_required(rss_list)
def report_index(request): def report_index(request):
number_tickets = Ticket.objects.all().count() number_tickets = Ticket.objects.all().count()
saved_query = request.GET.get('saved_query', None):
return render_to_response('helpdesk/report_index.html', return render_to_response('helpdesk/report_index.html',
RequestContext(request, { RequestContext(request, {
'number_tickets': number_tickets, 'number_tickets': number_tickets,
'saved_query': saved_query,
})) }))
report_index = staff_member_required(report_index) report_index = staff_member_required(report_index)
def run_report(request, report): def run_report(request, report):
if Ticket.objects.all().count() == 0: if Ticket.objects.all().count() == 0 or report not in ('queuemonth', 'usermonth', 'queuestatus', 'queuepriority', 'userstatus', 'userpriority', 'userqueue'):
return HttpResponseRedirect(reverse("helpdesk_report_index")) return HttpResponseRedirect(reverse("helpdesk_report_index"))
priority_sql = []
priority_columns = []
for p in Ticket.PRIORITY_CHOICES:
priority_sql.append("COUNT(CASE t.priority WHEN '%s' THEN t.id END) AS \"%s\"" % (p[0], p[1]._proxy____unicode_cast()))
priority_columns.append("%s" % p[1]._proxy____unicode_cast())
priority_sql = ", ".join(priority_sql)
status_sql = [] report_queryset = Ticket.objects.all().select_related()
status_columns = []
for s in Ticket.STATUS_CHOICES:
status_sql.append("COUNT(CASE t.status WHEN '%s' THEN t.id END) AS \"%s\"" % (s[0], s[1]._proxy____unicode_cast()))
status_columns.append("%s" % s[1]._proxy____unicode_cast())
status_sql = ", ".join(status_sql)
queue_sql = [] from_saved_query = False
queue_columns = [] saved_query = None
for q in Queue.objects.all():
queue_sql.append("COUNT(CASE t.queue_id WHEN '%s' THEN t.id END) AS \"%s\"" % (q.id, q.title)) if request.GET.get('saved_query', None):
queue_columns.append(q.title) from_saved_query = True
queue_sql = ", ".join(queue_sql) try:
saved_query = SavedSearch.objects.get(pk=request.GET.get('saved_query'))
except SavedSearch.DoesNotExist:
return HttpResponseRedirect(reverse('helpdesk_report_index'))
if not (saved_query.shared or saved_query.user == request.user):
return HttpResponseRedirect(reverse('helpdesk_report_index'))
import cPickle
from helpdesk.lib import b64decode
query_params = cPickle.loads(b64decode(str(saved_query.query)))
report_queryset = apply_query(report_queryset, query_params)
from collections import defaultdict
summarytable = defaultdict(int)
month_sql = []
months = ( months = (
'Jan', _('Jan'),
'Feb', _('Feb'),
'Mar', _('Mar'),
'Apr', _('Apr'),
'May', _('May'),
'Jun', _('Jun'),
'Jul', _('Jul'),
'Aug', _('Aug'),
'Sep', _('Sep'),
'Oct', _('Oct'),
'Nov', _('Nov'),
'Dec', _('Dec'),
) )
month_columns = []
# Throw an error if there are no tickets
first_ticket = Ticket.objects.all().order_by('created')[0] first_ticket = Ticket.objects.all().order_by('created')[0]
first_month = first_ticket.created.month first_month = first_ticket.created.month
first_year = first_ticket.created.year first_year = first_ticket.created.year
@ -804,115 +806,114 @@ def run_report(request, report):
periods = [] periods = []
year, month = first_year, first_month year, month = first_year, first_month
working = True working = True
periods.append("%s %s" % (months[month], year))
while working: while working:
temp = (year, month)
month += 1 month += 1
if month > 12: if month > 12:
year += 1 year += 1
month = 1 month = 1
if (year > last_year) or (month > last_month and year >= last_year): if (year > last_year) or (month > last_month and year >= last_year):
working = False working = False
periods.append((temp, (year, month))) periods.append("%s %s" % (months[month], year))
for (low_bound, upper_bound) in periods:
low_sqlmonth = '%s-%02i-01' % (low_bound[0], low_bound[1])
upper_sqlmonth = '%s-%02i-01' % (upper_bound[0], upper_bound[1])
desc = '%s %s' % (months[low_bound[1]-1], low_bound[0])
month_sql.append("""
COUNT(
CASE 1 = 1
WHEN (date(t.created) >= date('%s')
AND date(t.created) < date('%s')) THEN t.id END) AS "%s"
""" % (low_sqlmonth, upper_sqlmonth, desc))
month_columns.append(desc)
month_sql = ", ".join(month_sql)
queue_base_sql = """
SELECT q.title as queue, %s
FROM helpdesk_ticket t,
helpdesk_queue q
WHERE q.id = t.queue_id
GROUP BY queue
ORDER BY queue;
"""
user_base_sql = """
SELECT u.username as username, %s
FROM helpdesk_ticket t,
auth_user u
WHERE u.id = t.assigned_to_id
GROUP BY u.username
ORDER BY u.username;
"""
if report == 'userpriority': if report == 'userpriority':
sql = user_base_sql % priority_sql title = _('User by Priority')
columns = ['username'] + priority_columns col1heading = _('User')
title = 'User by Priority' possible_options = [t[1].__unicode__() for t in Ticket.PRIORITY_CHOICES]
charttype = 'bar'
elif report == 'userqueue': elif report == 'userqueue':
sql = user_base_sql % queue_sql title = _('User by Queue')
columns = ['username'] + queue_columns col1heading = _('User')
title = 'User by Queue' possible_options = [q.title.encode('utf-8') for q in Queue.objects.all()]
charttype = 'bar'
elif report == 'userstatus': elif report == 'userstatus':
sql = user_base_sql % status_sql title = _('User by Status')
columns = ['username'] + status_columns col1heading = _('User')
title = 'User by Status' possible_options = [s[1].__unicode__() for s in Ticket.STATUS_CHOICES]
charttype = 'bar'
elif report == 'usermonth': elif report == 'usermonth':
sql = user_base_sql % month_sql title = _('User by Month')
columns = ['username'] + month_columns col1heading = _('User')
title = 'User by Month' possible_options = periods
charttype = 'date'
elif report == 'queuepriority': elif report == 'queuepriority':
sql = queue_base_sql % priority_sql title = _('Queue by Priority')
columns = ['queue'] + priority_columns col1heading = _('Queue')
title = 'Queue by Priority' possible_options = [t[1].__unicode__() for t in Ticket.PRIORITY_CHOICES]
charttype = 'bar'
elif report == 'queuestatus': elif report == 'queuestatus':
sql = queue_base_sql % status_sql title = _('Queue by Status')
columns = ['queue'] + status_columns col1heading = _('Queue')
title = 'Queue by Status' possible_options = [s[1].__unicode__() for s in Ticket.STATUS_CHOICES]
charttype = 'bar'
elif report == 'queuemonth': elif report == 'queuemonth':
sql = queue_base_sql % month_sql title = _('Queue by Month')
columns = ['queue'] + month_columns col1heading = _('Queue')
title = 'Queue by Month' possible_options = periods
cursor = connection.cursor()
cursor.execute(sql)
report_output = query_to_dict(cursor.fetchall(), cursor.description)
data = []
for record in report_output:
line = []
for c in columns:
c = c.encode('utf-8')
line.append(record[c])
data.append(line)
if report in ('queuemonth', 'usermonth'):
chart_url = line_chart([columns] + data)
charttype = 'date' charttype = 'date'
elif report in ('queuestatus', 'queuepriority', 'userstatus', 'userpriority'):
chart_url = bar_chart([columns] + data)
charttype = 'bar'
else: for ticket in report_queryset:
chart_url = '' if report == 'userpriority':
charttype = '' metric1 = u'%s' % ticket.get_assigned_to
metric2 = u'%s' % ticket.get_priority_display()
elif report == 'userqueue':
metric1 = u'%s' % ticket.get_assigned_to
metric2 = u'%s' % ticket.queue.title
elif report == 'userstatus':
metric1 = u'%s' % ticket.get_assigned_to
metric2 = u'%s' % ticket.get_status_display()
elif report == 'usermonth':
metric1 = u'%s' % ticket.get_assigned_to
metric2 = u'%s %s' % (months[ticket.created.month], ticket.created.year)
elif report == 'queuepriority':
metric1 = u'%s' % ticket.queue.title
metric2 = u'%s' % ticket.get_priority_display()
elif report == 'queuestatus':
metric1 = u'%s' % ticket.queue.title
metric2 = u'%s' % ticket.get_status_display()
elif report == 'queuemonth':
metric1 = u'%s' % ticket.queue.title
metric2 = u'%s %s' % (months[ticket.created.month], ticket.created.year)
summarytable[metric1, metric2] += 1
table = []
header1 = sorted(set(list( i.encode('utf-8') for i,_ in summarytable.keys() )))
column_headings = [col1heading] + possible_options
# Pivot the data so that 'header1' fields are always first column
# in the row, and 'possible_options' are always the 2nd - nth columns.
for item in header1:
data = []
for hdr in possible_options:
data.append(summarytable[item, hdr])
table.append([item] + data)
return render_to_response('helpdesk/report_output.html', return render_to_response('helpdesk/report_output.html',
RequestContext(request, { RequestContext(request, {
'headings': columns,
'data': data,
'chart': chart_url,
'title': title, 'title': title,
'charttype': charttype, 'charttype': charttype,
'data': table,
'headings': column_headings,
'from_saved_query': from_saved_query,
'saved_query': saved_query,
})) }))
run_report = staff_member_required(run_report) run_report = staff_member_required(run_report)