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

-
-

Last ${days} days

-
-
- + + + Ticket Graph + ${Markup('<!--[if lt IE 7]>')} + + ${Markup('<![endif]-->')} + + +

Ticket Graph

+
+

Last ${days} days

+

+

+ Days Back: + + User: + + + +
+

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