diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5240b31 --- /dev/null +++ b/COPYING @@ -0,0 +1,28 @@ +Copyright (C) 2012 Colin Snover +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index 24acf64..0000000 --- a/README.md +++ /dev/null @@ -1,9 +0,0 @@ -TracTicketGraph -=============== - -Makes a graph of tickets. Installation w/ easy_install is easy. - -License -------- - -New BSD License (c) 2012 Colin Snover. diff --git a/setup.py b/setup.py index 536a771..801d356 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,33 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 Colin Snover +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. from setuptools import setup setup( - name = 'TracTicketGraph', - version = '1.0', - packages = ['ticketgraph'], - package_data = { 'ticketgraph' : [ 'htdocs/*.*', 'templates/*.*' ] }, + name='TracTicketGraph', + version='1.0.1', + packages=['ticketgraph'], + package_data={'ticketgraph': ['htdocs/*.*', 'templates/*.*']}, - 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.', - license = 'MIT', - keywords = 'trac plugin ticket statistics graph', - classifiers = [ + author='Colin Snover', + author_email='tracplugins@zetafleet.com', + description='Graphs Trac tickets over time', + url='https://github.com/trac-hacks/TracTicketGraph', + long_description="A Trac plugin that displays a visual graph of ticket " + "changes over time.", + license='MIT', + keywords='trac plugin ticket statistics graph', + classifiers=[ 'Framework :: Trac', ], - - install_requires = ['Trac'], - - entry_points = { + install_requires=['Trac'], + entry_points={ 'trac.plugins': [ 'ticketgraph = ticketgraph', ], diff --git a/ticketgraph/templates/ticketgraph.html b/ticketgraph/templates/ticketgraph.html index cb21ad5..08d342a 100644 --- a/ticketgraph/templates/ticketgraph.html +++ b/ticketgraph/templates/ticketgraph.html @@ -1,21 +1,22 @@ + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - - - 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

+
+
+ diff --git a/ticketgraph/ticketgraph.py b/ticketgraph/ticketgraph.py index ffd1f63..c65a3ac 100644 --- a/ticketgraph/ticketgraph.py +++ b/ticketgraph/ticketgraph.py @@ -1,55 +1,71 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 Colin Snover +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. import datetime import math import pkg_resources -from genshi.builder import tag from trac.core import * -from trac.web import IRequestHandler -from trac.web.chrome import INavigationContributor, ITemplateProvider, \ - add_script, add_script_data from trac.perm import IPermissionRequestor from trac.util.datefmt import to_utimestamp, utc +from trac.util.html import html from trac.util.translation import _ +from trac.web import IRequestHandler +from trac.web.chrome import INavigationContributor, ITemplateProvider, \ + add_script, add_script_data + class TicketGraphModule(Component): - implements(IPermissionRequestor, IRequestHandler, INavigationContributor, ITemplateProvider) + + implements(INavigationContributor, IPermissionRequestor, IRequestHandler, + ITemplateProvider) # IPermissionRequestor methods + def get_permission_actions(self): - return [ 'TICKET_GRAPH' ] + return ['TICKET_GRAPH'] # INavigationContributor methods + def get_active_navigation_item(self, req): return 'ticketgraph' def get_navigation_items(self, req): if 'TICKET_GRAPH' in req.perm: yield ('mainnav', 'ticketgraph', - tag.a(_('Ticket Graph'), href=req.href.ticketgraph())) + html.a(_("Ticket Graph"), href=req.href.ticketgraph())) # ITemplateProvider methods + def get_htdocs_dirs(self): - return [ ('ticketgraph', pkg_resources.resource_filename(__name__, 'htdocs')) ] + return [('ticketgraph', + pkg_resources.resource_filename(__name__, 'htdocs'))] def get_templates_dirs(self): - return [ pkg_resources.resource_filename(__name__, 'templates') ] + return [pkg_resources.resource_filename(__name__, 'templates')] # IRequestHandler methods + def match_request(self, req): return req.path_info == '/ticketgraph' def process_request(self, req): req.perm.require('TICKET_GRAPH') - today = datetime.datetime.combine(datetime.date.today(), datetime.time(tzinfo=utc)) + today = datetime.datetime.combine(datetime.date.today(), + datetime.time(tzinfo=utc)) days = int(req.args.get('days', 30)) # 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_end = to_utimestamp(today) + 86400000000 db = self.env.get_read_db() cursor = db.cursor() @@ -62,30 +78,36 @@ def process_request(self, req): } # 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), 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)) 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 ' \ - '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)) + cursor.execute( + 'SELECT COUNT(DISTINCT ticket), FLOOR(time / 86400000000) * 86400000 ' + '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)) for count, timestamp in cursor: series['reopenedTickets'][float(timestamp)] = float(count) # number of closed tickets for the time period, grouped by day (ms) - cursor.execute('SELECT COUNT(DISTINCT ticket), FLOOR(time / 86400000000) * 86400000 ' \ - '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)) + cursor.execute( + 'SELECT COUNT(DISTINCT ticket), FLOOR(time / 86400000000) * 86400000 ' + '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)) for count, timestamp in cursor: series['closedTickets'][float(timestamp)] = float(count) # number of open tickets at the end of the reporting period - cursor.execute('SELECT COUNT(*) FROM ticket WHERE status <> \'closed\'') + cursor.execute( + 'SELECT COUNT(*) FROM ticket WHERE status <> \'closed\'') open_tickets = cursor.fetchone()[0] open_ts = math.floor(ts_end / 1000) @@ -105,12 +127,11 @@ def process_request(self, req): for i in series: keys = series[i].keys() keys.sort() - data[i] = [ (k, series[i][k]) for k in keys ] + data[i] = [(k, series[i][k]) for k in keys] add_script(req, 'ticketgraph/jquery.flot.min.js') add_script(req, 'ticketgraph/jquery.flot.stack.min.js') add_script(req, 'ticketgraph/ticketgraph.js') add_script_data(req, data) - return 'ticketgraph.html', { 'days': days }, None - + return 'ticketgraph.html', {'days': days}, None