Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more functionalities and some eye candies #8

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
author = 'Colin Snover',
author_email = '[email protected]',
description = 'Graphs Trac tickets over time',
long_description = 'A Trac plugin that displays a visual graph of ticket changes over time.',
long_description = 'A Trac plugin that displays a visual graph of ticket changes over time, modified by Fabrizio Parrella ([email protected]).',
license = 'MIT',
keywords = 'trac plugin ticket statistics graph',
classifiers = [
Expand Down
157 changes: 119 additions & 38 deletions ticketgraph/htdocs/ticketgraph.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,121 @@
var tracGraphPlot = null;
$(document).ready(function() {
var graph = $('#placeholder').width(640).height(400),
barSettings = { show: true, barWidth: 86400000 };
$.plot($('#placeholder'),
[
{
data: closedTickets,
label: 'Closed tickets',
bars: barSettings,
color: 1
},
{
data: openedTickets,
label: 'New tickets',
bars: barSettings,
color: 2,
stack: true
},
{
data: reopenedTickets,
label: 'Reopened tickets',
bars: barSettings,
color: 3,
stack: true
},
{
data: openTickets,
label: 'Open tickets',
yaxis: 2,
lines: { show: true },
color: 0
}
],
{
xaxis: { mode: 'time', minTickSize: [1, "day"] },
yaxis: { min: 0, label: 'Tickets' },
y2axis: { min: 0 },
legend: { position: 'nw' }
});
var lineTick = 86400000;
if(typeof stack_graph !== 'undefined' && stack_graph == true){
lineTick /= 3.5;
for(var k in closedTickets){
closedTickets[k][0] += lineTick+5000000;
}
for(var k in workedTickets){
workedTickets[k][0] += (lineTick+5000000)*2;
}
}
$('#owner')[0].options.add(new Option("All","%"));
for(var i in users){
if(users[i][1]==""||users[i][1]==null){
users[i][1] = users[i][0];
}
$('#owner')[0].options.add(new Option(users[i][1],users[i][0]));
}
$('#owner').val(owner);
var graph = $('#placeholder').width(800).height(500);
var data = [
{
data: openedTickets,
label: 'New tickets',
color: '#66cd00',
stack: true,
idx: 0
},
{
data: reopenedTickets,
label: 'Reopened tickets',
color: '#458b00',
stack: true,
idx: 1
},
{
data: closedTickets,
label: 'Closed tickets',
color: '#8b0000',
idx: 2
}, {
data: workedTickets,
label: 'Worked tickets',
color: '#45458b',
idx: 3
}
];
if(owner==="" || owner==="%"){
data.push({
data: openTickets,
label: 'Open tickets',
yaxis: 2,
lines: { show: true, steps: false },
bars: {show: false},
shadowSize: 0,
color: '#333',
idx: 4
});
}
var options = {
series:{
bars: { show: true, barWidth: lineTick, align: 'center', stack: false}
},
xaxis: { mode: 'time', minTickSize: [1, "day"] },
grid: { hoverable: true },
yaxis: { label: 'Tickets' },
y2axis: { min: 0 },
legend: {
container:$("#legend-container"),
position: 'ne',
labelFormatter: function(label, series){
return '<a href="#" onClick="tracGraphTogglePlot('+series.idx+'); return false;">'+label+'</a>';
}
}
};
tracGraphPlot = $.plot($('#placeholder'), data, options);

$("<div id='tooltip'></div>").css({
position: "absolute",
display: "none",
border: "1px solid #fdd",
padding: "2px",
"background-color": "#fee",
opacity: 0.80
}).appendTo("body");


$("#placeholder").bind("plothover", function (event, pos, item) {
if (item) {
var x = item.datapoint[0],
y = Math.abs(item.datapoint[1]);
$("#tooltip").html(tracGraphTimeConverter(x) + '<br />' + item.series.label + " : " + y)
.css({top: item.pageY+5, left: item.pageX+5})
.fadeIn(200);
} else {
$("#tooltip").hide();
}
});
});
function tracGraphTimeConverter(timestamp){
var a = new Date(timestamp);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var time = month + ' ' + date + ', ' + year;
return time;
}
function tracGraphTogglePlot(seriesIdx){
var someData = tracGraphPlot.getData();
if(typeof someData[seriesIdx].data_old === 'undefined'){
someData[seriesIdx].data_old=someData[seriesIdx].data;
someData[seriesIdx].data=[];
}else{
someData[seriesIdx].data=someData[seriesIdx].data_old;
delete(someData[seriesIdx].data_old);
}
tracGraphPlot.setData(someData);
tracGraphPlot.draw();
}
41 changes: 27 additions & 14 deletions ticketgraph/templates/ticketgraph.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<title>Ticket Graph</title>
${Markup('&lt;!--[if lt IE 7]&gt;')}
<script type="text/javascript" src="${href.chrome('ticketgraph/excanvas.min.js')}"></script>
${Markup('&lt;![endif]--&gt;')}
</head>
<body>
<h1>Ticket Graph</h1>
<div id="content">
<h2>Last ${days} days</h2>
<div id="placeholder"></div>
</div>
</body>
<xi:include href="layout.html" />
<head>
<title>Ticket Graph</title>
${Markup('&lt;!--[if lt IE 7]&gt;')}
<script type="text/javascript" src="${href.chrome('ticketgraph/excanvas.min.js')}"></script>
${Markup('&lt;![endif]--&gt;')}
</head>
<body>
<h1>Ticket Graph</h1>
<div id="content">
<h2>Last ${days} days</h2>
<p>
<form method="get" id="ticketgraph">
Days Back:
<select name="days">
<option py:for="option in [30,60,90,180,365]" value="$option" selected="${option == days or None}">$option</option>
</select>
User:
<select name="owner" id="owner"></select>
<label><input type="checkbox" name="sg" id="graph-sg" value="1" checked="${sg or None}" />Stack Graph</label>
<input type="submit" value="Graph" />
</form>
</p>
<div id="legend-container"></div>
<div id="placeholder"></div>
</div>
</body>
</html>
79 changes: 62 additions & 17 deletions ticketgraph/ticketgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,71 +46,116 @@ def process_request(self, req):
today = datetime.datetime.combine(datetime.date.today(), datetime.time(tzinfo=utc))

days = int(req.args.get('days', 30))
owner = req.args.get('owner', "%")
stack_graph={};
stack_graph['stack_graph'] = bool(int(req.args.get('sg', 0)))
# These are in microseconds; the data returned is in milliseconds
# because it gets passed to flot
ts_start = to_utimestamp(today - datetime.timedelta(days=days))
ts_end = to_utimestamp(today) + 86400000000;
ts_utc_delta = math.ceil((datetime.datetime.utcnow()-datetime.datetime.now()).total_seconds())*1000;

db = self.env.get_read_db()
cursor = db.cursor()

series = {
'openedTickets': {},
'closedTickets': {},
'workedTickets': {},
'reopenedTickets': {},
'openTickets': {}
}
args = [ts_start, ts_end];
tix_args = [ts_start, ts_end];
where = '';
tix_where = '';
if owner=="" :
owner = "%"

if owner!="%" :
where += 'AND author LIKE %s ';
tix_where += 'AND reporter LIKE %s ';
args.append(owner)
tix_args.append(owner)


# number of created tickets for the time period, grouped by day (ms)
cursor.execute('SELECT COUNT(DISTINCT id), FLOOR(time / 86400000000) * 86400000 ' \
'AS date FROM ticket WHERE time BETWEEN %s AND %s ' \
'GROUP BY date ORDER BY date ASC', (ts_start, ts_end))
cursor.execute('SELECT COUNT(DISTINCT id),( UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(time/1000000)))*1000) ' \
'AS date FROM ticket WHERE time BETWEEN %s AND %s ' + tix_where +
'GROUP BY date ORDER BY date ASC', tix_args)
for count, timestamp in cursor:
series['openedTickets'][float(timestamp)] = float(count)

# number of reopened tickets for the time period, grouped by day (ms)
cursor.execute('SELECT COUNT(DISTINCT ticket), FLOOR(time / 86400000000) * 86400000 ' \
cursor.execute('SELECT COUNT(DISTINCT ticket), UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(time/1000000)))*1000 ' \
'AS date FROM ticket_change WHERE field = \'status\' AND newvalue = \'reopened\' ' \
'AND time BETWEEN %s AND %s ' \
'GROUP BY date ORDER BY date ASC', (ts_start, ts_end))
'AND time BETWEEN %s AND %s ' + where +
'GROUP BY date ORDER BY date ASC', args)
for count, timestamp in cursor:
series['reopenedTickets'][float(timestamp)] = float(count)

# number of worked tickets for the time period, grouped by day (ms)
cursor.execute('SELECT COUNT(DISTINCT ticket), UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(time/1000000)))*1000 ' \
'AS date FROM ticket_change WHERE ' \
'time BETWEEN %s AND %s ' + where +
'GROUP BY date ORDER BY date ASC', args)
for count, timestamp in cursor:
series['workedTickets'][float(timestamp)] = float((1 if stack_graph['stack_graph'] else -1)*count)


# number of closed tickets for the time period, grouped by day (ms)
cursor.execute('SELECT COUNT(DISTINCT ticket), FLOOR(time / 86400000000) * 86400000 ' \
cursor.execute('SELECT COUNT(DISTINCT ticket), UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(time/1000000)))*1000 ' \
'AS date FROM ticket_change WHERE field = \'status\' AND newvalue = \'closed\' ' \
'AND time BETWEEN %s AND %s ' \
'GROUP BY date ORDER BY date ASC', (ts_start, ts_end))
'AND time BETWEEN %s AND %s ' + where +
'GROUP BY date ORDER BY date ASC', args)
for count, timestamp in cursor:
series['closedTickets'][float(timestamp)] = float(count)
series['closedTickets'][float(timestamp)] = float((1 if stack_graph['stack_graph'] else -1)*count)

# number of open tickets at the end of the reporting period
cursor.execute('SELECT COUNT(*) FROM ticket WHERE status <> \'closed\'')
if owner!='%' :
cursor.execute('SELECT COUNT(*) FROM ticket WHERE status <> \'closed\' AND owner LIKE %s ', [owner])
else:
cursor.execute('SELECT COUNT(*) FROM ticket WHERE status <> \'closed\' ')


open_tickets = cursor.fetchone()[0]
open_ts = math.floor(ts_end / 1000)
open_ts = math.floor((ts_end) / 1000)+ts_utc_delta

# series['openTickets'][open_ts] = open_tickets

while open_ts >= math.floor(ts_start / 1000):
if open_ts in series['closedTickets']:
open_tickets += series['closedTickets'][open_ts]
open_tickets += (1 if stack_graph['stack_graph'] else -1)*series['closedTickets'][open_ts]
if open_ts in series['openedTickets']:
open_tickets -= series['openedTickets'][open_ts]
if open_ts in series['reopenedTickets']:
open_tickets -= series['reopenedTickets'][open_ts]

series['openTickets'][open_ts] = open_tickets
open_ts -= 86400000
series['openTickets'][open_ts-86400000] = open_tickets

new_ts_utc_delta = math.ceil((datetime.datetime.utcfromtimestamp(open_ts/1000)-datetime.datetime.fromtimestamp(open_ts/1000)).total_seconds())*1000;

open_ts -= 86400000 + ts_utc_delta - new_ts_utc_delta
ts_utc_delta = new_ts_utc_delta

data = {}
for i in series:
keys = series[i].keys()
keys.sort()
data[i] = [ (k, series[i][k]) for k in keys ]

data['owner'] = owner
data['users']={}
i=0;
for user in self.env.get_known_users():
data['users'][i] = user
i=i+1

add_script(req, 'ticketgraph/jquery.flot.min.js')
# add_script(req, 'http://people.iola.dk/olau/flot/jquery.flot.js')
add_script(req, 'ticketgraph/jquery.flot.stack.min.js')
add_script(req, 'ticketgraph/ticketgraph.js')
add_script_data(req, data)
add_script_data(req, stack_graph)

return 'ticketgraph.html', { 'days': days }, None

return 'ticketgraph.html', { 'days': days, 'sg': stack_graph['stack_graph'] }, None