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