Author: gjm
Date: Tue Apr 17 09:45:10 2012
New Revision: 1327017
URL: http://svn.apache.org/viewvc?rev=1327017&view=rev
Log:
dashboard: First version of layout markups used in milestone view
Modified:
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
Modified:
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html?rev=1327017&r1=1327016&r2=1327017&view=diff
==============================================================================
---
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
(original)
+++
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
Tue Apr 17 09:45:10 2012
@@ -67,7 +67,8 @@
<py:match path="bh:layout">
<py:choose test="">
<py:when test="bhdb">
- ${bhdb.embed_layout(context, layout=select('@urn'),
schema=select('bh:schema'), widgets=select('bh:widgets'))}
+ <!--! TODO: Implement nested XML tags (e.g. bh:w and bh:l) -->
+ ${bhdb.embed_layout(context, layout=unicode(select('@urn')),
schema=unicode(select('bh:schema/text()')),
widgets=unicode(select('bh:widgets/text()')))}
</py:when>
<py:otherwise>
${bhnotfound()}
Modified:
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py?rev=1327017&r1=1327016&r2=1327017&view=diff
==============================================================================
---
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
(original)
+++
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
Tue Apr 17 09:45:10 2012
@@ -145,23 +145,23 @@ class DashboardModule(Component):
]
}
],
- 'widgets' : [
- {
+ 'widgets' : {
+ 0: {
'args' : ['Container', None,
{'args' : {'layout' : 'bootstrap_btnbar',
'schema' : '''
{
"toolbar" : [
["Products", null],
- ["My Tickets", 2],
- ["All tickets", 1],
+ ["My Tickets", "w3"],
+ ["All tickets", "w2"],
["|", null],
["Projects", null],
- ["Components", 0]
+ ["Components", "w1"]
],
"active" : 1,
- "widgets" : [
- {
+ "widgets" : {
+ "w1" : {
"args" : [
"TicketFieldCloud",
null,
@@ -169,7 +169,7 @@ class DashboardModule(Component):
"field" : "component",
"verbose" : true}}]
},
- {
+ "w2" : {
"args" : [
"TicketQuery", null,
{"args" : {
@@ -182,7 +182,7 @@ class DashboardModule(Component):
}],
"altlinks" : false
},
- {
+ "w3" : {
"args" : [
"TicketQuery", null,
{"args" : {
@@ -196,21 +196,21 @@ class DashboardModule(Component):
}],
"altlinks" : false
}
- ]
+ }
}
''',
'title' : _("Dashboard")
}
}]
},
- {
+ 1: {
'args' : ['Timeline', None, {'args' : {}}]
},
- ]
+ }
}
# Public API
- def expand_layout_data(self, req, layout_name, schema):
+ def expand_layout_data(self, req, layout_name, schema, embed=False):
"""Determine the template needed to render a specific layout
and the data needed to place the widgets at expected
location.
@@ -219,7 +219,8 @@ class DashboardModule(Component):
ctx = Context.from_request(req)
template = layout.expand_layout(layout_name, ctx, {
- 'schema' : schema
+ 'schema' : schema,
+ 'embed' : embed
})['template']
return template, schema
@@ -227,34 +228,32 @@ class DashboardModule(Component):
"""Expand raw widget data and format it for use in template
"""
# TODO: Implement dynamic dashboard specification
- widgets_spec = schema.pop('widgets', [])
- widgets_index = dict([k, list(v)] for k,v in \
- groupby(widgets_spec, lambda w : w['args'][0]))
+ widgets_spec = schema.pop('widgets', {})
+ widgets_index = dict([wnm, wp] \
+ for wp in DashboardSystem(self.env).widget_providers \
+ for wnm in wp.get_widgets()
+ )
+ self.log.debug("Bloodhound: Widget index %s" % (widgets_index,))
ctx = Context.from_request(req)
- try :
- for wp in DashboardSystem(self.env).widget_providers:
- for wnm in wp.get_widgets():
- substitutions = widgets_index.pop(wnm, [])
- i = -1
- for i, w in enumerate(substitutions):
- w['c'] = wp
- w['args'][1] = ctx
- self.log.debug('Widget %s (%s substitutions)', wnm, i + 1)
- if not widgets_index:
- raise StopIteration("No more widgets")
- except StopIteration:
- pass
- if len(widgets_index) > 0:
- raise LookupError('Unknown provider for widgets %s',
- ' , '.join(widgets_index.keys()))
+ for w in widgets_spec.itervalues():
+ w['c'] = widgets_index[w['args'][0]]
+ w['args'][1] = ctx
+ self.log.debug("Bloodhound: Widget specs %s" % (widgets_spec,))
chrome = Chrome(self.env)
render = chrome.render_template
- data_strm = (w['c'].render_widget(*w['args']) for w in widgets_spec)
- return [{'title' : data['title'],
+ data_strm = ( (k, w, w['c'].render_widget(*w['args'])) \
+ for k,w in widgets_spec.iteritems())
+ return dict([k, {'title' : data['title'],
'content' : render(wctx.req, template, data['data'],
fragment=True),
'ctxtnav' : w.get('ctxtnav', True) and data.get('ctxtnav') or
None,
- 'altlinks' : w.get('altlinks', True) and data.get('altlinks')
or None} \
- for w, (template, data, wctx) in izip(widgets_spec, data_strm)]
+ 'altlinks' : w.get('altlinks', True) and data.get('altlinks')
or None}] \
+ for k, w, (template, data, wctx) in data_strm)
+
+#------------------------------------------------------
+# Dashboard Helpers to be used in templates
+#------------------------------------------------------
+
+XMLNS_DASHBOARD_UI = 'http://issues.apache.org/bloodhound/wiki/Ui/Dashboard'
class DashboardChrome:
"""Helper functions providing access to dashboard infrastructure
@@ -264,7 +263,7 @@ class DashboardChrome:
def __init__(self, env):
self.env = env
- def embed_layout(self, context, **kwargs):
+ def embed_layout(self, context, layout, **kwargs):
"""Render layout and widgets
:param context: Rendering context
@@ -273,7 +272,26 @@ class DashboardChrome:
:param widgets: Widgets definition
"""
dbmod = DashboardModule(self.env)
- raise NotImplementedError("DashboardChrome.embed_layout")
+ schema = kwargs.get('schema', {})
+ if isinstance(schema, basestring):
+ schema = _json.loads(schema)
+ widgets = kwargs.get('widgets')
+ if widgets is not None:
+ # TODO: Use this one once widgets markup parser will be ready
+ #widgets = parse_widgets_markup(widgets)
+ if isinstance(widgets, basestring):
+ widgets = _json.loads(widgets)
+ else:
+ widgets = {}
+ schema['widgets'] = widgets
+ template, layout_data = dbmod.expand_layout_data(
+ context.req, layout, schema, True)
+ widgets = dbmod.expand_widget_data(context.req, layout_data)
+ return Chrome(self.env).render_template(context.req, template,
+ dict(context=context, layout=layout_data,
+ widgets=widgets, title='',
+ default={'height':dbmod.default_widget_height or
None}),
+ fragment=True)
def expand_widget(self, context, widget):
"""Render single widget
@@ -283,8 +301,44 @@ class DashboardChrome:
"""
dbmod = DashboardModule(self.env)
if isinstance(widget['args'], basestring):
- widgets['args'] = json.loads(widget['args'])
+ widget['args'] = _json.loads(widget['args'])
return dbmod.expand_widget_data(
context.req,
- {'widgets' : [widget]}
+ {'widgets' : { 0 : widget }}
)[0]
+
+#------------------------------------------------------
+# Stream processors
+#------------------------------------------------------
+
+def parse_widgets_markup(stream):
+ """Parse Genshi Markup for a set of widgets
+ """
+ s = iter(stream)
+
+ # Check that stream starts by opening bh:widgets tag
+ token, data, pos = s.next()
+ assert token == 'START'
+ assert data[0].namespace == XMLNS_DASHBOARD_UI
+ assert data[1].localname == 'widgets'
+
+ # Continue parsing bh:w tag
+ def find(_s, tkn, ns, localname):
+ for token, data, pos in _s:
+ if token == tkn and data[0].namespace != ns \
+ and data[1].localname != localname:
+ return token, data, pos
+ else:
+ return None
+
+ i = find(s, 'START', XMLNS_DASHBOARD_UI, 'widget')
+
+ # TODO: Finish parsing widgets markup
+
+ #if i is not None:
+ # i = find()
+
+def parse_widget_markup(stream):
+ """Parse Genshi Markup for a single widget
+ """
+
Modified:
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html?rev=1327017&r1=1327016&r2=1327017&view=diff
==============================================================================
---
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
(original)
+++
incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
Tue Apr 17 09:45:10 2012
@@ -50,9 +50,90 @@
</py:def>
<xi:include href="widget_progress.html"
py:with="caption = milestone_caption(); legend = True;
- desc = wiki_to_html(context, milestone.description)"/>
- <div class="alert">
- <span class="label label-warning">TODO</span> Include button toolbar.
+ desc = wiki_to_html(context, milestone.description);"/>
+ <div py:if="'MILESTONE_MODIFY' in perm(milestone.resource) or
+ 'MILESTONE_DELETE' in perm(milestone.resource) or
+ attachments.can_create">
+ <span class="label label-info">Actions</span>
+ <form py:if="'MILESTONE_MODIFY' in perm(milestone.resource)"
+ method="get" action="" id="editmilestone"
+ style="display: inline-block">
+ <input type="hidden" name="action" value="edit" />
+ <input type="submit" value="${_('Edit milestone')}" class="btn" />
+ </form>
+ <form py:if="'MILESTONE_DELETE' in perm(milestone.resource)"
+ method="get" action="" id="deletemilestone"
+ style="display: inline-block">
+ <input type="hidden" name="action" value="delete" />
+ <input type="submit" value="${_('Delete milestone')}" class="btn"
/>
+ </form>
+ <xi:include href="attach_file_form.html" py:with="alist =
attachments" />
+ </div>
+ <div class="row">
+ <bh:layout urn="bootstrap_btnbar">
+ <bh:schema>
+ {
+ "toolbar" : [
+ ["My Tickets", "mtb1"],
+ ["All Tickets", "mtb2"],
+ ["|", null],
+ ["Projects", null],
+ ["Components", "mtb3"]
+ ],
+ "active" : 0
+ }
+ </bh:schema>
+ <bh:widgets>
+ {
+ "mtb1" : {
+ "args" : ["TicketQuery", null, {
+ "args" : {
+ "max" : 10,
+ "query" :
"milestone=${milestone.name}&owner=$$USER&status!=closed&group=time&col=id&col=summary&col=owner&col=status&col=priority&order=priority&groupdesc=1&desc=1",
+ "title" : "My tickets (milestone
${milestone.name})"
+ }
+ }]
+ },
+ "mtb2" : {
+ "args" : ["TicketQuery", null, {
+ "args" : {
+ "max" : 10,
+ "query" :
"milestone=${milestone.name}&status!=closed&group=time&col=id&col=summary&col=owner&col=status&col=priority&order=priority&groupdesc=1&desc=1",
+ "title" : "All tickets (milestone
${milestone.name})"
+ }
+ }]
+ },
+ "mtb3" : {
+ "args" : ["TicketFieldCloud", null, {
+ "args" : {
+ "field" : "component",
+ "verbose" : true
+ }
+ }]
+ }
+ }
+ </bh:widgets>
+ <!--! bh:w urn="TicketQuery" id="mtb1">
+ <bh:args>
+ <bh:arg name="max">10</bh:arg>
+ <bh:arg
name="query">milestone=${value_of(milestone.name)}&owner=$USER&status!=closed&group=time&col=id&col=summary&col=owner&col=status&col=priority&order=priority&groupdesc=1&desc=1</bh:arg>
+ <bh:arg name="title">My tickets (milestone
${milestone.name})</bh:arg>
+ </bh:args>
+ </bh:w>
+ <bh:w urn="TicketQuery" id="mtb2">
+ <bh:args>
+ <bh:arg name="max">10</bh:arg>
+ <bh:arg
name="query">milestone=${value_of(milestone.name)}&status!=closed&group=time&col=id&col=summary&col=owner&col=status&col=priority&order=priority&groupdesc=1&desc=1</bh:arg>
+ <bh:arg name="title">All tickets (milestone
${milestone.name})</bh:arg>
+ </bh:args>
+ </bh:w>
+ <bh:w urn="TicketFieldCloud" id="mtb3">
+ <bh:args>
+ <bh:arg name="field">component</bh:arg>
+ <bh:arg name="verbose">true</bh:arg>
+ </bh:args>
+ </bh:w-->
+ </bh:layout>
</div>
</div>
<div class="span4">