Skip to content

Commit

Permalink
ProgressMeterMacro: Major code revision and unification across the su…
Browse files Browse the repository at this point in the history
…pported Trac versions.

git-svn-id: https://trac-hacks.org/svn/progressmetermacro@11719 7322e99d-02ea-0310-aa39-e9a107903beb
  • Loading branch information
andrejtokarcik committed Jul 9, 2012
1 parent afbfdd8 commit d7d8319
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 50 deletions.
83 changes: 58 additions & 25 deletions 0.11/progressmeter/macro.py
Expand Up @@ -3,15 +3,17 @@
import os
import re

from trac.config import ExtensionOption
from trac.config import ExtensionOption, ExtensionPoint
from trac.core import *
from trac.ticket.query import Query
from trac.ticket.roadmap import ITicketGroupStatsProvider, \
apply_ticket_permissions, get_ticket_stats
from trac.util import TracError
from trac.web.chrome import Chrome, ITemplateProvider, add_stylesheet
from trac.wiki.api import IWikiMacroProvider, parse_args
from trac.wiki.macros import WikiMacroBase


def query_stats_data(req, stat, constraints, grouped_by='component',
group=None):
def query_href(extra_args):
Expand All @@ -22,7 +24,7 @@ def query_href(extra_args):
return {'stats': stat,
'stats_href': query_href(stat.qry_args),
'interval_hrefs': [query_href(interval['qry_args'])
for interval in stat.intervals]}
for interval in stat.intervals]}

class ProgressMeterMacro(WikiMacroBase):
"""Progress meter wiki macro plugin for Trac
Expand All @@ -32,54 +34,85 @@ class ProgressMeterMacro(WikiMacroBase):
"""
implements(ITemplateProvider)

stats_provider = ExtensionOption('progressmeter', 'stats_provider',
ITicketGroupStatsProvider,
'DefaultTicketGroupStatsProvider',
_sp = ExtensionOption('progressmeter', 'stats_provider',
ITicketGroupStatsProvider,
'DefaultTicketGroupStatsProvider',
"""Name of the component implementing `ITicketGroupStatsProvider`,
which is used to collect statistics on groups of tickets
for meters generated by the ProgressMeterMacro plugin.""")

_ticket_re = re.compile(r'/ticket/([0-9]+)$')
def _this_ticket(self, req):
assert req.path_info != '/newticket', "Attempt to preview a progress" \
" meter pointing to this ticket, which does not exist yet."

match = re.match(r'/ticket/([0-9]+)$', req.path_info)
match = self._ticket_re.match(req.path_info)
if match:
return match.group(1)
else:
assert req.path_info == '/newticket', "The `self` " \
"keyword is permitted in ticket descriptions only."
return None

def expand_macro(self, formatter, name, content):
req = formatter.req

# Parse arguments
def _parse_macro_content(self, content, req):
args, kwargs = parse_args(content, strict=False)
assert not args and not ('status' in kwargs or 'format' in kwargs), \
"Invalid input!"
# hack the `format` kwarg in order to display all-tickets stats
# when no kwargs are supplied
kwargs['format'] = 'count'

# special case for values equal to 'self': replace with current
# ticket number, if available
kwargs['max'] = 0
kwargs['order'] = 'id'
kwargs['col'] = 'id'

# special case for values equal to 'self': replace with current ticket
# number, if available
preview = False
for key in kwargs.keys():
if kwargs[key] == 'self':
current_ticket = self._this_ticket(req)
if current_ticket: kwargs[key] = current_ticket
if current_ticket:
kwargs[key] = current_ticket
else:
# id=0 basically causes a dummy preview of the meter
# to be rendered
preview = True
kwargs = {'id': 0}
break

try:
spkw = kwargs.pop('stats_provider')
xtnpt = ExtensionPoint(ITicketGroupStatsProvider)

found = False
for impl in xtnpt.extensions(self):
if impl.__class__.__name__ == spkw:
found = True
stats_provider = impl
break

if not found:
raise TracError("Supplied stats provider does not exist!")
except KeyError:
# if the `stats_provider` keyword argument is not provided,
# propagate the stats provider defined in the config file
stats_provider = self._sp

return stats_provider, kwargs, preview


def expand_macro(self, formatter, name, content):
req = formatter.req
stats_provider, kwargs, preview = self._parse_macro_content(content, req)

# Create & execute the query string
qstr = '&'.join(['%s=%s' % item
for item in kwargs.iteritems()])
query = Query.from_string(self.env, qstr, max=0)
for item in kwargs.iteritems()])
query = Query.from_string(self.env, qstr)

# Calculate stats
qres = query.execute(req)
tickets = apply_ticket_permissions(self.env, req, qres)

stats = get_ticket_stats(self.stats_provider, tickets)
stats = get_ticket_stats(stats_provider, tickets)
stats_data = query_stats_data(req, stats, query.constraints)

# ... and finally display them
add_stylesheet(req, 'common/css/roadmap.css')
chrome = Chrome(self.env)
stats_data.update({'preview': preview}) # displaying a preview?
return chrome.render_template(req, 'progressmeter.html', stats_data,
fragment=True)

Expand Down
29 changes: 17 additions & 12 deletions 0.11/progressmeter/templates/progressmeter.html
@@ -1,16 +1,21 @@
<div
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
class="milestone">
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
class="milestone">

<!--!
NOTE: <py:def>s from macros.html are going to be moved to
separate files (see the warning in the beginning of the macros.html file)
-->
<xi:include href="macros.html" />

<!--!
NOTE: <py:def>s from macros.html are going to be moved to
separate files (see the warning in the beginning of the macros.html file)
-->
<xi:include href="macros.html" />
<div py:if="preview" class="notice system-message">
A preview of the progress meter is displayed below
since this ticket, which has not been created yet,
is being referred to:
</div>

<div class="info">
${progress_bar(stats, interval_hrefs, stats_href=stats_href)}
</div>
</div>
<div class="info trac-progress">
${progress_bar(stats, interval_hrefs, stats_href=stats_href)}
</div>
</div>
3 changes: 3 additions & 0 deletions 0.11/setup.cfg
@@ -0,0 +1,3 @@
[egg_info]
tag_build =
tag_svn_revision = true
16 changes: 7 additions & 9 deletions 0.12/progressmeter/macro.py
Expand Up @@ -48,17 +48,14 @@ def _this_ticket(self, req):
return match.group(1)
else:
assert req.path_info == '/newticket', "The `self` " \
"keyword can be used only in ticket descriptions."
"keyword is permitted in ticket descriptions only."
return None

def _parse_macro_content(self, content, req):
args, kwargs = parse_args(content, strict=False)
assert not args and not ('status' in kwargs or 'format' in kwargs), \
"Invalid input!"

# hack the `format` kwarg in order to display all-tickets stats when
# no kwargs are supplied
kwargs['format'] = 'count'
kwargs['max'] = 0
kwargs['order'] = 'id'
kwargs['col'] = 'id'

# special case for values equal to 'self': replace with current ticket
# number, if available
Expand Down Expand Up @@ -102,11 +99,12 @@ def expand_macro(self, formatter, name, content):
# Create & execute the query string
qstr = '&'.join(['%s=%s' % item
for item in kwargs.iteritems()])
query = Query.from_string(self.env, qstr, max=0)
query = Query.from_string(self.env, qstr)
try:
# XXX: simplification, may cause problems with more complex queries
constraints = query.constraints[0]
except IndexError:
constraints = query.constraints
constraints = {}

# Calculate stats
qres = query.execute(req)
Expand Down
6 changes: 2 additions & 4 deletions 0.12/progressmeter/templates/progressmeter.html
Expand Up @@ -3,15 +3,13 @@
xmlns:xi="http://www.w3.org/2001/XInclude"
class="milestone">

<div py:if="preview" class="warning system-message">
<div py:if="preview" class="notice system-message">
A preview of the progress meter is displayed below
since this ticket, which has not been created yet,
is being referred to:
</div>

<div class="info trac-progress">
<xi:include href="progress_bar.html"
py:with="stats = stats; interval_hrefs = interval_hrefs;
stats_href = stats_href" />
<xi:include href="progress_bar.html" />
</div>
</div>

0 comments on commit d7d8319

Please sign in to comment.