diff --git a/setup.py b/setup.py
index 536a771..412e97a 100644
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
author = 'Colin Snover',
author_email = 'tracplugins@zetafleet.com',
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 (fabrizio@bibivu.com).',
license = 'MIT',
keywords = 'trac plugin ticket statistics graph',
classifiers = [
diff --git a/ticketgraph/htdocs/ticketgraph.js b/ticketgraph/htdocs/ticketgraph.js
index 813e20f..6360c3d 100644
--- a/ticketgraph/htdocs/ticketgraph.js
+++ b/ticketgraph/htdocs/ticketgraph.js
@@ -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 ''+label+'';
+ }
+ }
+ };
+ tracGraphPlot = $.plot($('#placeholder'), data, options);
+
+ $("
").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) + '
' + 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();
+}
diff --git a/ticketgraph/templates/ticketgraph.html b/ticketgraph/templates/ticketgraph.html
index cb21ad5..4c578c9 100644
--- a/ticketgraph/templates/ticketgraph.html
+++ b/ticketgraph/templates/ticketgraph.html
@@ -4,18 +4,31 @@
-
-
- Ticket Graph
- ${Markup('<!--[if lt IE 7]>')}
-
- ${Markup('<![endif]-->')}
-
-
- Ticket Graph
-
-
+
+
+ Ticket Graph
+ ${Markup('<!--[if lt IE 7]>')}
+
+ ${Markup('<![endif]-->')}
+
+
+ Ticket Graph
+
+
Last ${days} days
+
+
+
+
+
+
+
diff --git a/ticketgraph/ticketgraph.py b/ticketgraph/ticketgraph.py
index ffd1f63..9d533c5 100644
--- a/ticketgraph/ticketgraph.py
+++ b/ticketgraph/ticketgraph.py
@@ -46,10 +46,14 @@ 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()
@@ -57,49 +61,82 @@ def process_request(self, req):
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:
@@ -107,10 +144,18 @@ def process_request(self, req):
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