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)
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):
"""
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_body %}
{% ifequal number_tickets 0 %}{% blocktrans %}
<h2>Reports &amp; Statistics</h2>
<h2>{% trans "Reports &amp; Statistics" %}</h2>
<p>You haven't created any tickets yet, so you cannot run any reports.</p>
{% endblocktrans %}
{% ifequal number_tickets 0 %}
<p>{% trans "You haven't created any tickets yet, so you cannot run any reports." %}</p>
{% else %}
{% blocktrans %}
<h2>Reports &amp; Statistics</h2>
<ul>
<li>User<ul>
<li>{% trans "Reports By User" %}<ul>
<li><a href='userpriority/'>by Priority</a></li>
<li><a href='userqueue/'>by Queue</a></li>
<li><a href='userstatus/'>by Status</a></li>
<li><a href='usermonth/'>by Month</a></li>
<li><a href='userpriority/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Priority" %}</a></li>
<li><a href='userqueue/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Queue" %}</a></li>
<li><a href='userstatus/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Status" %}</a></li>
<li><a href='usermonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
</ul></li>
<li>Queue<ul>
<li>{% trans "Reports By Queue" %}<ul>
<li><a href='queuepriority/'>by Priority</a></li>
<li><a href='queuestatus/'>by Status</a></li>
<li><a href='queuemonth/'>by Month</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='queuemonth/{% if saved_query %}?saved_query={{ saved_query }}{% endif %}'>{% trans "by Month" %}</a></li>
</ul></li>
</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 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.settings import HAS_TAG_SUPPORT
@ -745,54 +745,56 @@ rss_list = staff_member_required(rss_list)
def report_index(request):
number_tickets = Ticket.objects.all().count()
saved_query = request.GET.get('saved_query', None):
return render_to_response('helpdesk/report_index.html',
RequestContext(request, {
'number_tickets': number_tickets,
'saved_query': saved_query,
}))
report_index = staff_member_required(report_index)
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"))
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 = []
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)
report_queryset = Ticket.objects.all().select_related()
queue_sql = []
queue_columns = []
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))
queue_columns.append(q.title)
queue_sql = ", ".join(queue_sql)
from_saved_query = False
saved_query = None
if request.GET.get('saved_query', None):
from_saved_query = True
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 = (
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
_('Jan'),
_('Feb'),
_('Mar'),
_('Apr'),
_('May'),
_('Jun'),
_('Jul'),
_('Aug'),
_('Sep'),
_('Oct'),
_('Nov'),
_('Dec'),
)
month_columns = []
# Throw an error if there are no tickets
first_ticket = Ticket.objects.all().order_by('created')[0]
first_month = first_ticket.created.month
first_year = first_ticket.created.year
@ -804,115 +806,114 @@ def run_report(request, report):
periods = []
year, month = first_year, first_month
working = True
periods.append("%s %s" % (months[month], year))
while working:
temp = (year, month)
month += 1
if month > 12:
year += 1
month = 1
if (year > last_year) or (month > last_month and year >= last_year):
working = False
periods.append((temp, (year, month)))
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;
"""
periods.append("%s %s" % (months[month], year))
if report == 'userpriority':
sql = user_base_sql % priority_sql
columns = ['username'] + priority_columns
title = 'User by Priority'
title = _('User by Priority')
col1heading = _('User')
possible_options = [t[1].__unicode__() for t in Ticket.PRIORITY_CHOICES]
charttype = 'bar'
elif report == 'userqueue':
sql = user_base_sql % queue_sql
columns = ['username'] + queue_columns
title = 'User by Queue'
title = _('User by Queue')
col1heading = _('User')
possible_options = [q.title.encode('utf-8') for q in Queue.objects.all()]
charttype = 'bar'
elif report == 'userstatus':
sql = user_base_sql % status_sql
columns = ['username'] + status_columns
title = 'User by Status'
title = _('User by Status')
col1heading = _('User')
possible_options = [s[1].__unicode__() for s in Ticket.STATUS_CHOICES]
charttype = 'bar'
elif report == 'usermonth':
sql = user_base_sql % month_sql
columns = ['username'] + month_columns
title = 'User by Month'
title = _('User by Month')
col1heading = _('User')
possible_options = periods
charttype = 'date'
elif report == 'queuepriority':
sql = queue_base_sql % priority_sql
columns = ['queue'] + priority_columns
title = 'Queue by Priority'
title = _('Queue by Priority')
col1heading = _('Queue')
possible_options = [t[1].__unicode__() for t in Ticket.PRIORITY_CHOICES]
charttype = 'bar'
elif report == 'queuestatus':
sql = queue_base_sql % status_sql
columns = ['queue'] + status_columns
title = 'Queue by Status'
title = _('Queue by Status')
col1heading = _('Queue')
possible_options = [s[1].__unicode__() for s in Ticket.STATUS_CHOICES]
charttype = 'bar'
elif report == 'queuemonth':
sql = queue_base_sql % month_sql
columns = ['queue'] + month_columns
title = 'Queue by Month'
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)
title = _('Queue by Month')
col1heading = _('Queue')
possible_options = periods
charttype = 'date'
elif report in ('queuestatus', 'queuepriority', 'userstatus', 'userpriority'):
chart_url = bar_chart([columns] + data)
charttype = 'bar'
else:
chart_url = ''
charttype = ''
for ticket in report_queryset:
if report == 'userpriority':
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',
RequestContext(request, {
'headings': columns,
'data': data,
'chart': chart_url,
'title': title,
'charttype': charttype,
'data': table,
'headings': column_headings,
'from_saved_query': from_saved_query,
'saved_query': saved_query,
}))
run_report = staff_member_required(run_report)