You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by gj...@apache.org on 2012/04/16 15:00:36 UTC

svn commit: r1326583 - in /incubator/bloodhound/trunk/bloodhound_dashboard: ./ bhdashboard/ bhdashboard/layouts/templates/ bhdashboard/web_ui/ bhdashboard/web_ui/templates/ bhdashboard/web_ui/ticket/ bhdashboard/widgets/ bhdashboard/widgets/templates/

Author: gjm
Date: Mon Apr 16 13:00:35 2012
New Revision: 1326583

URL: http://svn.apache.org/viewvc?rev=1326583&view=rev
Log:
dashboard: Activity feed in milestone view using Bloodhound widget markup; initial version of progress bar widget

Added:
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py   (with props)
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py   (with props)
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html   (with props)
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py   (with props)
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py   (with props)
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html   (with props)
Removed:
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui.py
Modified:
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py
    incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py
    incubator/bloodhound/trunk/bloodhound_dashboard/setup.py

Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py?rev=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py Mon Apr 16 13:00:35 2012
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+
+r"""Project dashboard for Apache(TM) Bloodhound
+
+Provide a single namespace to access JSON functions.
+"""
+
+try :
+    from json import *
+except ImportError:
+    from simplejson import *

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py
------------------------------------------------------------------------------
    svn:eol-style = native

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=1326583&r1=1326582&r2=1326583&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 Mon Apr 16 13:00:35 2012
@@ -2,8 +2,11 @@
     xmlns="http://www.w3.org/1999/xhtml"
     xmlns:py="http://genshi.edgewall.org/"
     xmlns:xi="http://www.w3.org/2001/XInclude"
+    xmlns:bh="http://issues.apache.org/bloodhound/wiki/Ui/Dashboard"
     py:strip="" >
 
+  <!-- Helper functions (py:def) -->
+
   <div py:def="widget_container(w)" role="application">
     <h1 style="display: inline;">${w.title}</h1>
     <py:if test="w.ctxtnav">
@@ -27,4 +30,70 @@
     <br/>
     ${w.content}
   </div>
+
+  <!-- Widget markup (py:match) -->
+
+  <div py:def="bhnotfound()" class="alert alert-error">
+    <span class="label label-important">Error</span>
+    Dashboard data is missing . 
+    Is <code>bhdashboard.web_ui.DashboardModule</code> component disabled?
+  </div>
+
+  <!--
+  Sample layout tag 
+
+  <bh:layout type="LayoutName">
+    <bh:schema>
+      JSON object describing positioning and ...
+    </bh:schema>
+    <bh:widgets>
+      <bh:w id="simple_widget" type="WidgetName" altlinks="false">
+        <bh:args>
+          <bh:arg name="arg1">value1</bh:arg>
+          <bh:arg name="arg2">value2</bh:arg>
+        </bh:args>
+      </bh:w>
+      <bh:l id="nested_layout" type="LayoutName">
+        <bh:schema>
+          JSON object describing positioning and ...
+        </bh:schema>
+        <bh:widgets>
+          ... Same as before ...
+        </bh:widgets>
+      </bh:l>
+    </bh:widgets>
+  </bh:layout>
+  -->
+  <py:match path="bh:layout">
+    <py:choose test="">
+      <py:when test="bhdb">
+        ${bhdb.embed_layout(context, layout=select('@type'), schema=select('bh:schema'), widgets=select('bh:widgets'))}
+      </py:when>
+      <py:otherwise>
+        ${bhnotfound()}
+      </py:otherwise>
+    </py:choose>
+  </py:match>
+  <!-- 
+  Sample widget tag
+
+  <bh:widget id="simple_widget" type="WidgetName" altlinks="false">
+    <bh:args>
+      <bh:arg name="arg1">value1</bh:arg>
+      <bh:arg name="arg2">value2</bh:arg>
+    </bh:args>
+  </bh:widget>
+
+  -->
+  <py:match path="bh:widget">
+    <py:choose test="">
+      <py:when test="bhdb"
+          py:with="wnm = unicode(select('@type')); args = unicode(select('bh:args/text()')) or {}; altlinks = unicode(select('@altlinks')) != 'false'; ctxtnav = unicode(select('@ctxtnav')) != 'false';">
+        ${widget_container(bhdb.expand_widget(context, dict(args=[wnm, None, {'args': args}], altlinks=altlinks, ctxtnav=ctxtnav)))}
+      </py:when>
+      <py:otherwise>
+        ${bhnotfound()}
+      </py:otherwise>
+    </py:choose>
+  </py:match>
 </html>

Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py?rev=1326583&r1=1326582&r2=1326583&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py (original)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py Mon Apr 16 13:00:35 2012
@@ -144,6 +144,24 @@ def pretty_wrapper(wrapped, *decorators)
     return update_wrapper(wrapper, wrapped)
 
 #------------------------------------------------------
+#    Trac core
+#------------------------------------------------------
+
+def resolve_ep_class(interface, component, clsnm, **kwargs):
+    r"""Retrieve the class implementing an interface (by name)
+    """
+    ep = ExtensionPoint(interface)
+    for c in ep.extensions(component):
+        if c.__class__.__name__ == clsnm :
+            return c
+    else:
+        if 'default' in kwargs:
+            return kwargs['default']
+        else:
+            raise LookupError('No match found for class %s implementing %s' % 
+                    (clsnm, interface) )
+
+#------------------------------------------------------
 #    Context information
 #------------------------------------------------------
 

Added: 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=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py Mon Apr 16 13:00:35 2012
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+
+r"""Project dashboard for Apache(TM) Bloodhound
+
+Implementing dashboard user interface.
+"""
+
+__metaclass__ = type
+
+from itertools import izip
+import pkg_resources
+import re
+
+from genshi.builder import tag
+from trac.core import Component, implements
+from trac.config import Option, IntOption
+from trac.mimeview.api import Context
+from trac.util.translation import _
+from trac.ticket.query import QueryModule
+from trac.ticket.report import ReportModule
+from trac.util.compat import groupby
+from trac.web.api import IRequestHandler, IRequestFilter
+from trac.web.chrome import add_ctxtnav, add_stylesheet, Chrome, \
+                            INavigationContributor, ITemplateProvider
+
+from bhdashboard.api import DashboardSystem
+from bhdashboard import json
+
+class DashboardModule(Component):
+    """Web frontend for dashboard infrastructure.
+    """
+    implements(IRequestHandler, IRequestFilter, INavigationContributor, \
+                ITemplateProvider)
+
+    mainnav_label = Option('dashboard', 'mainnav', 'Dashboard', \
+                            """Dashboard label in mainnav""")
+    default_widget_height = IntOption('widgets', 'default_height', 320, \
+                            """Default widget height in pixels""")
+
+    # IRequestFilter methods
+
+    def pre_process_request(self, req, handler):
+        """Always returns the request handler unchanged.
+        """
+        return handler
+
+    def post_process_request(self, req, template, data, content_type):
+        """Inject dashboard helpers in data.
+        """
+        if data is not None :
+            data['bhdb'] = DashboardChrome(self.env)
+        return template, data, content_type
+
+    # IRequestHandler methods
+    def match_request(self, req):
+        """Match dashboard prefix"""
+        return bool(re.match(r'^/dashboard(/.)?', req.path_info))
+
+    def process_request(self, req):
+        """Initially this will render static widgets. With time it will be 
+        more and more dynamic and flexible.
+        """
+        if self.env[QueryModule] is not None:
+            add_ctxtnav(req, _('Custom Query'), req.href.query())
+        if self.env[ReportModule] is not None:
+            add_ctxtnav(req, _('Reports'), req.href.report())
+        template, layout_data = self.expand_layout_data(req, 
+            'bootstrap_grid', self.DASHBOARD_SCHEMA)
+        widgets = self.expand_widget_data(req, layout_data) 
+        return template, {
+                    'context' : Context.from_request(req),
+                    'layout' : layout_data,
+                    'widgets' : widgets,
+                    'title' : _(self.mainnav_label),
+                    'default' : {
+                            'height' : self.default_widget_height or None
+                        }
+                }, None
+
+    # INavigationContributor methods
+    def get_active_navigation_item(self, req):
+        """Highlight dashboard mainnav item.
+        """
+        return 'dashboard'
+
+    def get_navigation_items(self, req):
+        """Add an item in mainnav to access global dashboard
+        """
+        if 'DASHBOARD_VIEW' in req.perm:
+            yield ('mainnav', 'dashboard', 
+                    tag.a(_(self.mainnav_label), href=req.href.dashboard()))
+
+    # ITemplateProvider methods
+    def get_htdocs_dirs(self):
+        """List `htdocs` dirs for dashboard and widgets.
+        """
+        resource_filename = pkg_resources.resource_filename
+        return [
+                 ('dashboard', resource_filename('bhdashboard', 'htdocs')),
+                 #('widgets', resource_filename('bhdashboard.widgets', 'htdocs'))
+                 ('layouts', resource_filename('bhdashboard.layouts', 'htdocs'))
+                 ]
+
+    def get_templates_dirs(self):
+        """List `templates` folders for dashboard and widgets.
+        """
+        resource_filename = pkg_resources.resource_filename
+        return [resource_filename('bhdashboard.layouts', 'templates'),
+                resource_filename('bhdashboard.web_ui', 'templates'),
+                resource_filename('bhdashboard.widgets', 'templates')]
+
+    # Temp vars
+    DASHBOARD_SCHEMA = {
+            'div' : [
+                    {
+                        '_class' : 'row',
+                        'div' : [
+                                {
+                                    '_class' : 'span8',
+                                    'widgets' : [0]
+                                },
+                                {
+                                    '_class' : 'span4',
+                                    'widgets' : [1]
+                                }
+                            ]
+                    }
+                ],
+            'widgets' : [
+                    {
+                        'args' : ['Container', None, 
+                                {'args' : {'layout' : 'bootstrap_btnbar',
+                                        'schema' : '''
+                                        {
+                                          "toolbar" : [
+                                              ["Products", null],
+                                              ["My Tickets", 2],
+                                              ["All tickets", 1],
+                                              ["|", null],
+                                              ["Projects", null],
+                                              ["Components", 0]
+                                            ],
+                                          "active" : 1,
+                                          "widgets" : [
+                                            {
+                                              "args" : [
+                                                  "TicketFieldCloud", 
+                                                  null, 
+                                                  {"args" : {
+                                                      "field" : "component",
+                                                      "verbose" : true}}]
+                                            },
+                                            {
+                                              "args" : [
+                                                  "TicketQuery", null, 
+                                                  {"args" : {
+                                                      "max" : 10,
+                                                      "query" : "''' + 
+                'status!=closed&group=time&col=id&col=summary&col=owner' \
+                '&col=status&col=priority&order=priority&groupdesc=1&desc=1' +
+                                                      '''",
+                                                      "title" : "All Tickets"}
+                                                  }],
+                                              "altlinks" : false
+                                            },
+                                            {
+                                              "args" : [
+                                                  "TicketQuery", null, 
+                                                  {"args" : {
+                                                      "max" : 10,
+                                                      "query" : "''' + 
+                'status!=closed&group=time&col=id&col=summary&col=owner' \
+                '&col=status&col=priority&order=priority&groupdesc=1&desc=1' \
+                '&owner=$USER' +
+                                                      '''",
+                                                      "title" : "My Tickets"}
+                                                  }],
+                                              "altlinks" : false
+                                            }
+                                          ]
+                                        }
+                                        ''',
+                                        'title' : _("Dashboard")
+                                        }
+                                }]
+                    },
+                    {
+                        'args' : ['Timeline', None, {'args' : {}}]
+                    },
+                ]
+        }
+
+    # Public API
+    def expand_layout_data(self, req, layout_name, schema):
+        """Determine the template needed to render a specific layout
+        and the data needed to place the widgets at expected
+        location.
+        """
+        layout = DashboardSystem(self.env).resolve_layout(layout_name)
+
+        ctx = Context.from_request(req)
+        template = layout.expand_layout(layout_name, ctx, {
+                'schema' : schema
+            })['template']
+        return template, schema
+
+    def expand_widget_data(self, req, schema):
+        """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]))
+        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()))
+        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'], 
+                '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)]
+
+class DashboardChrome:
+    """Helper functions providing access to dashboard infrastructure 
+    in Genshi templates. Useful to reuse layouts and widgets across
+    website.
+    """
+    def __init__(self, env):
+        self.env = env
+
+    def embed_layout(self, context, **kwargs):
+        """Render layout and widgets
+
+        :param context: Rendering context
+        :param layout: Identifier of target layout
+        :param schema: Data describing widget positioning
+        :param widgets: Widgets definition
+        """
+        dbmod = DashboardModule(self.env)
+        raise NotImplementedError("DashboardChrome.embed_layout")
+
+    def expand_widget(self, context, widget):
+        """Render single widget
+
+        :param context: Rendering context
+        :param widget: Widget definition
+        """
+        dbmod = DashboardModule(self.env)
+        if isinstance(widget['args'], basestring):
+            widgets['args'] = json.loads(widget['args'])
+        return dbmod.expand_widget_data(
+                    context.req,
+                    {'widgets' : [widget]}
+                )[0]

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 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=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html Mon Apr 16 13:00:35 2012
@@ -0,0 +1,28 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/"
+      xmlns:bh="http://issues.apache.org/bloodhound/wiki/Ui/Dashboard"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+  <xi:include href="layout.html" />
+  <xi:include href="widget_macros.html" />
+  <head>
+    <title>Milestone ${milestone.name}</title>
+    <link py:if="'MILESTONE_MODIFY' in perm(milestone.resource)" rel="alternate" type="application/x-wiki"
+          title="Edit this milestone" href="${href.milestone(milestone.name, action='edit')}" />
+  </head>
+
+  <body>
+    <div class="row">
+      <div class="span8">
+        <div class="alert">
+          <span class="label label-warning">TODO</span> Include milestone data.
+        </div>
+      </div>
+      <div class="span4">
+        <bh:widget type="Timeline" />
+      </div>
+    </div>
+  </body>
+</html>

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
------------------------------------------------------------------------------
    svn:mime-type = text/html

Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py?rev=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py Mon Apr 16 13:00:35 2012
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+
+r"""Project dashboard for Apache(TM) Bloodhound
+
+Overriden version of Trac ticket interface 
+"""

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py?rev=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py Mon Apr 16 13:00:35 2012
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+r"""Roadmap view for Apache(TM) Bloodhound
+
+Customizing roadmap user interface.
+"""
+
+__metaclass__ = type
+
+from itertools import izip
+import pkg_resources
+import re
+
+from genshi.builder import tag
+from trac.core import Component, implements
+from trac.mimeview.api import Context
+from trac.ticket.roadmap import MilestoneModule, RoadmapModule
+from trac.util.translation import _
+from trac.web.api import IRequestFilter
+from trac.web.chrome import add_ctxtnav, add_stylesheet
+
+from bhdashboard.api import DashboardSystem
+
+class BloodhoundMilestoneModule(Component):
+    """Override default milestone views.
+    """
+    implements(IRequestFilter)
+
+    # IRequestFilter methods
+
+    def pre_process_request(self, req, handler):
+        """Always returns the request handler unchanged.
+        """
+        return handler
+
+    def post_process_request(self, req, template, data, content_type):
+        """Customize milestone view.
+        """
+        mdl = self.env[MilestoneModule]
+        if mdl is not None and mdl.match_request(req):
+            return 'bhmilestone.html', data, content_type
+        else:
+            return template, data, content_type

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py?rev=1326583&r1=1326582&r2=1326583&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py (original)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py Mon Apr 16 13:00:35 2012
@@ -32,7 +32,7 @@ from genshi.builder import tag
 from trac.core import implements, TracError
 from trac.mimeview.api import Context
 from trac.resource import Resource, ResourceNotFound
-from trac.ticket.query import QueryModule
+from trac.ticket.query import Query, QueryModule
 from trac.util.translation import _
 from trac.web.api import RequestDone
 
@@ -136,3 +136,11 @@ class TicketQueryWidget(WidgetBase):
 
     render_widget = pretty_wrapper(render_widget, check_widget_name)
 
+#--------------------------------------
+# Query functions and methods
+#--------------------------------------
+
+def exec_query(env, req, qstr='status!=closed'):
+    """ Perform a ticket query, returning a list of ticket ID's. 
+    """
+    return Query.from_string(env, qstr).execute(req)

Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html?rev=1326583&view=auto
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html (added)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html Mon Apr 16 13:00:35 2012
@@ -0,0 +1,8 @@
+
+<div class="well"
+    xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:py="http://genshi.edgewall.org/"
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+  $legend
+  <div class="pull-right">${'%d%%' % stats.done_percent}</div>
+</div>

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
------------------------------------------------------------------------------
    svn:mime-type = text/html

Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py?rev=1326583&r1=1326582&r2=1326583&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py (original)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py Mon Apr 16 13:00:35 2012
@@ -29,13 +29,18 @@ from itertools import imap, islice
 from genshi.builder import tag
 from trac.core import implements, TracError
 from trac.ticket.api import TicketSystem
+from trac.ticket.roadmap import apply_ticket_permissions, get_ticket_stats, \
+                            ITicketGroupStatsProvider, RoadmapModule
 from trac.util.translation import _
+from trac.web.chrome import add_stylesheet
 
 from bhdashboard.api import DateField, EnumField, InvalidWidgetArgument, \
                             ListField
+from bhdashboard.widgets.query import exec_query
 from bhdashboard.util import WidgetBase, check_widget_name, \
-                              dummy_request, merge_links, minmax, \
-                              pretty_wrapper, trac_version, trac_tags
+                            dummy_request, merge_links, minmax, \
+                            pretty_wrapper, resolve_ep_class, \
+                            trac_version, trac_tags
 
 class TicketFieldCloudWidget(WidgetBase):
     """Display a tag cloud representing frequency of values assigned to 
@@ -116,3 +121,70 @@ class TicketFieldCloudWidget(WidgetBase)
 
     render_widget = pretty_wrapper(render_widget, check_widget_name)
 
+class TicketGroupStatsWidget(WidgetBase):
+    """Display progress bar illustrating statistics gathered on a group
+    of tickets.
+    """
+    def get_widget_params(self, name):
+        """Return a dictionary containing arguments specification for
+        the widget with specified name.
+        """
+        return {
+                'query' : {
+                        'default' : 'status!=closed',
+                        'desc' : """Query string""",
+                    },
+                'stats_provider' : {
+                        'desc' : """Name of the component implementing
+        `ITicketGroupStatsProvider`, which is used to collect statistics 
+        on groups of tickets.""",
+                        'default' : 'DefaultTicketGroupStatsProvider'
+                    },
+                'skin' : {
+                        'desc' : """Look and feel of the progress bar""",
+                        'type' : EnumField('info', 'success', 'warning', 
+                                'danger',
+                                'info-stripped', 'success-stripped', 
+                                'warning-stripped', 'danger-stripped')
+                    },
+                'title' : {
+                        'desc' : """Widget title""",
+                    },
+                'legend' : {
+                        'desc' : """Text on top of the progress bar""",
+                    },
+                'desc' : {
+                        'desc' : """Descriptive (wiki) text""",
+                    },
+            }
+    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)
+
+    def render_widget(self, name, context, options):
+        """Prepare ticket stats
+        """
+        req = context.req
+        params = ('query', 'stats_provider', 'skin', 'title', 'legend', 'desc')
+        qstr, pnm, skin, title, legend, desc = \
+                self.bind_params(name, options, *params)
+        statsp = resolve_ep_class(ITicketGroupStatsProvider, self, pnm,
+                                    default=RoadmapModule(self.env).stats_provider)
+        skin = (skin or '').split('-', 2)
+        progress_css = 'progress ' + ' '.join('progress-'+c for c in skin if c)
+
+        tickets = exec_query(self.env, req, qstr)
+        tickets = apply_ticket_permissions(self.env, req, tickets)
+        stat = get_ticket_stats(self.stats_provider, tickets)
+
+        add_stylesheet('dashboard/bootstrap.css')
+        return 'widget_progress.html', \
+                {
+                    'title' : title,
+                    'data' : dict(
+                            desc=desc,
+                            legend=legend,
+                            stats=stat,
+                        ), 
+                }, \
+                context
+
+    render_widget = pretty_wrapper(render_widget, check_widget_name)

Modified: incubator/bloodhound/trunk/bloodhound_dashboard/setup.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/setup.py?rev=1326583&r1=1326582&r2=1326583&view=diff
==============================================================================
--- incubator/bloodhound/trunk/bloodhound_dashboard/setup.py (original)
+++ incubator/bloodhound/trunk/bloodhound_dashboard/setup.py Mon Apr 16 13:00:35 2012
@@ -102,6 +102,14 @@ PKG_INFO = {'bhdashboard' : ('bhdashboar
                             # Package data
                             ['templates/*', 'htdocs/*'],
                           ), 
+            'bhdashboard.web_ui' : ('bhdashboard/web_ui',     # Package dir
+                            # Package data
+                            ['templates/*', 'htdocs/*'],
+                          ), 
+            'bhdashboard.web_ui.ticket' : ('bhdashboard/web_ui/ticket',     # Package dir
+                            # Package data
+                            [],
+                          ), 
             'bhdashboard.tests' : ('bhdashboard/tests',     # Package dir
                             # Package data
                             ['data/**'],
@@ -113,6 +121,7 @@ ENTRY_POINTS = r"""
                bhdashboard.api = bhdashboard.api
                bhdashboard.layouts.bootstrap = bhdashboard.layouts.bootstrap
                bhdashboard.web_ui = bhdashboard.web_ui
+               bhdashboard.web_ui.ticket.roadmap = bhdashboard.web_ui.ticket.roadmap
                bhdashboard.widgets.containers = bhdashboard.widgets.containers
                bhdashboard.widgets.query = bhdashboard.widgets.query
                bhdashboard.widgets.report = bhdashboard.widgets.report



Re: svn commit: r1326583 - in /incubator/bloodhound/trunk/bloodhound_dashboard: ./ bhdashboard/ bhdashboard/layouts/templates/ bhdashboard/web_ui/ bhdashboard/web_ui/templates/ bhdashboard/web_ui/ticket/ bhdashboard/widgets/ bhdashboard/widgets/templates/

Posted by Gary <ga...@wandisco.com>.
Hi,

This commit is a little on the large side as it includes a refactor of 
the dashboard web_ui, but it provides a timeline for the milestone view 
and the beginnings of a progress bar.

Cheers,
     Gary


On 04/16/2012 02:00 PM, gjm@apache.org wrote:
> Author: gjm
> Date: Mon Apr 16 13:00:35 2012
> New Revision: 1326583
>
> URL: http://svn.apache.org/viewvc?rev=1326583&view=rev
> Log:
> dashboard: Activity feed in milestone view using Bloodhound widget markup; initial version of progress bar widget
>
> Added:
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py   (with props)
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py   (with props)
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html   (with props)
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py   (with props)
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py   (with props)
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html   (with props)
> Removed:
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui.py
> Modified:
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/layouts/templates/widget_macros.html
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py
>      incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py
>      incubator/bloodhound/trunk/bloodhound_dashboard/setup.py
>
> Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py?rev=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,30 @@
> +#!/usr/bin/env python
> +# -*- coding: UTF-8 -*-
> +
> +#  Licensed to the Apache Software Foundation (ASF) under one
> +#  or more contributor license agreements.  See the NOTICE file
> +#  distributed with this work for additional information
> +#  regarding copyright ownership.  The ASF licenses this file
> +#  to you under the Apache License, Version 2.0 (the
> +#  "License"); you may not use this file except in compliance
> +#  with the License.  You may obtain a copy of the License at
> +#
> +#   http://www.apache.org/licenses/LICENSE-2.0
> +#
> +#  Unless required by applicable law or agreed to in writing,
> +#  software distributed under the License is distributed on an
> +#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
> +#  KIND, either express or implied.  See the License for the
> +#  specific language governing permissions and limitations
> +#  under the License.
> +
> +
> +r"""Project dashboard for Apache(TM) Bloodhound
> +
> +Provide a single namespace to access JSON functions.
> +"""
> +
> +try :
> +    from json import *
> +except ImportError:
> +    from simplejson import *
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/_json.py
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> 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=1326583&r1=1326582&r2=1326583&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 Mon Apr 16 13:00:35 2012
> @@ -2,8 +2,11 @@
>       xmlns="http://www.w3.org/1999/xhtml"
>       xmlns:py="http://genshi.edgewall.org/"
>       xmlns:xi="http://www.w3.org/2001/XInclude"
> +    xmlns:bh="http://issues.apache.org/bloodhound/wiki/Ui/Dashboard"
>       py:strip="">
>
> +<!-- Helper functions (py:def) -->
> +
>     <div py:def="widget_container(w)" role="application">
>       <h1 style="display: inline;">${w.title}</h1>
>       <py:if test="w.ctxtnav">
> @@ -27,4 +30,70 @@
>       <br/>
>       ${w.content}
>     </div>
> +
> +<!-- Widget markup (py:match) -->
> +
> +<div py:def="bhnotfound()" class="alert alert-error">
> +<span class="label label-important">Error</span>
> +    Dashboard data is missing .
> +    Is<code>bhdashboard.web_ui.DashboardModule</code>  component disabled?
> +</div>
> +
> +<!--
> +  Sample layout tag
> +
> +<bh:layout type="LayoutName">
> +<bh:schema>
> +      JSON object describing positioning and ...
> +</bh:schema>
> +<bh:widgets>
> +<bh:w id="simple_widget" type="WidgetName" altlinks="false">
> +<bh:args>
> +<bh:arg name="arg1">value1</bh:arg>
> +<bh:arg name="arg2">value2</bh:arg>
> +</bh:args>
> +</bh:w>
> +<bh:l id="nested_layout" type="LayoutName">
> +<bh:schema>
> +          JSON object describing positioning and ...
> +</bh:schema>
> +<bh:widgets>
> +          ... Same as before ...
> +</bh:widgets>
> +</bh:l>
> +</bh:widgets>
> +</bh:layout>
> +  -->
> +<py:match path="bh:layout">
> +<py:choose test="">
> +<py:when test="bhdb">
> +        ${bhdb.embed_layout(context, layout=select('@type'), schema=select('bh:schema'), widgets=select('bh:widgets'))}
> +</py:when>
> +<py:otherwise>
> +        ${bhnotfound()}
> +</py:otherwise>
> +</py:choose>
> +</py:match>
> +<!--
> +  Sample widget tag
> +
> +<bh:widget id="simple_widget" type="WidgetName" altlinks="false">
> +<bh:args>
> +<bh:arg name="arg1">value1</bh:arg>
> +<bh:arg name="arg2">value2</bh:arg>
> +</bh:args>
> +</bh:widget>
> +
> +  -->
> +<py:match path="bh:widget">
> +<py:choose test="">
> +<py:when test="bhdb"
> +          py:with="wnm = unicode(select('@type')); args = unicode(select('bh:args/text()')) or {}; altlinks = unicode(select('@altlinks')) != 'false'; ctxtnav = unicode(select('@ctxtnav')) != 'false';">
> +        ${widget_container(bhdb.expand_widget(context, dict(args=[wnm, None, {'args': args}], altlinks=altlinks, ctxtnav=ctxtnav)))}
> +</py:when>
> +<py:otherwise>
> +        ${bhnotfound()}
> +</py:otherwise>
> +</py:choose>
> +</py:match>
>   </html>
>
> Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py?rev=1326583&r1=1326582&r2=1326583&view=diff
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py (original)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/util.py Mon Apr 16 13:00:35 2012
> @@ -144,6 +144,24 @@ def pretty_wrapper(wrapped, *decorators)
>       return update_wrapper(wrapper, wrapped)
>
>   #------------------------------------------------------
> +#    Trac core
> +#------------------------------------------------------
> +
> +def resolve_ep_class(interface, component, clsnm, **kwargs):
> +    r"""Retrieve the class implementing an interface (by name)
> +    """
> +    ep = ExtensionPoint(interface)
> +    for c in ep.extensions(component):
> +        if c.__class__.__name__ == clsnm :
> +            return c
> +    else:
> +        if 'default' in kwargs:
> +            return kwargs['default']
> +        else:
> +            raise LookupError('No match found for class %s implementing %s' %
> +                    (clsnm, interface) )
> +
> +#------------------------------------------------------
>   #    Context information
>   #------------------------------------------------------
>
>
> Added: 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=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,290 @@
> +#!/usr/bin/env python
> +# -*- coding: UTF-8 -*-
> +
> +#  Licensed to the Apache Software Foundation (ASF) under one
> +#  or more contributor license agreements.  See the NOTICE file
> +#  distributed with this work for additional information
> +#  regarding copyright ownership.  The ASF licenses this file
> +#  to you under the Apache License, Version 2.0 (the
> +#  "License"); you may not use this file except in compliance
> +#  with the License.  You may obtain a copy of the License at
> +#
> +#   http://www.apache.org/licenses/LICENSE-2.0
> +#
> +#  Unless required by applicable law or agreed to in writing,
> +#  software distributed under the License is distributed on an
> +#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
> +#  KIND, either express or implied.  See the License for the
> +#  specific language governing permissions and limitations
> +#  under the License.
> +
> +
> +r"""Project dashboard for Apache(TM) Bloodhound
> +
> +Implementing dashboard user interface.
> +"""
> +
> +__metaclass__ = type
> +
> +from itertools import izip
> +import pkg_resources
> +import re
> +
> +from genshi.builder import tag
> +from trac.core import Component, implements
> +from trac.config import Option, IntOption
> +from trac.mimeview.api import Context
> +from trac.util.translation import _
> +from trac.ticket.query import QueryModule
> +from trac.ticket.report import ReportModule
> +from trac.util.compat import groupby
> +from trac.web.api import IRequestHandler, IRequestFilter
> +from trac.web.chrome import add_ctxtnav, add_stylesheet, Chrome, \
> +                            INavigationContributor, ITemplateProvider
> +
> +from bhdashboard.api import DashboardSystem
> +from bhdashboard import json
> +
> +class DashboardModule(Component):
> +    """Web frontend for dashboard infrastructure.
> +    """
> +    implements(IRequestHandler, IRequestFilter, INavigationContributor, \
> +                ITemplateProvider)
> +
> +    mainnav_label = Option('dashboard', 'mainnav', 'Dashboard', \
> +                            """Dashboard label in mainnav""")
> +    default_widget_height = IntOption('widgets', 'default_height', 320, \
> +                            """Default widget height in pixels""")
> +
> +    # IRequestFilter methods
> +
> +    def pre_process_request(self, req, handler):
> +        """Always returns the request handler unchanged.
> +        """
> +        return handler
> +
> +    def post_process_request(self, req, template, data, content_type):
> +        """Inject dashboard helpers in data.
> +        """
> +        if data is not None :
> +            data['bhdb'] = DashboardChrome(self.env)
> +        return template, data, content_type
> +
> +    # IRequestHandler methods
> +    def match_request(self, req):
> +        """Match dashboard prefix"""
> +        return bool(re.match(r'^/dashboard(/.)?', req.path_info))
> +
> +    def process_request(self, req):
> +        """Initially this will render static widgets. With time it will be
> +        more and more dynamic and flexible.
> +        """
> +        if self.env[QueryModule] is not None:
> +            add_ctxtnav(req, _('Custom Query'), req.href.query())
> +        if self.env[ReportModule] is not None:
> +            add_ctxtnav(req, _('Reports'), req.href.report())
> +        template, layout_data = self.expand_layout_data(req,
> +            'bootstrap_grid', self.DASHBOARD_SCHEMA)
> +        widgets = self.expand_widget_data(req, layout_data)
> +        return template, {
> +                    'context' : Context.from_request(req),
> +                    'layout' : layout_data,
> +                    'widgets' : widgets,
> +                    'title' : _(self.mainnav_label),
> +                    'default' : {
> +                            'height' : self.default_widget_height or None
> +                        }
> +                }, None
> +
> +    # INavigationContributor methods
> +    def get_active_navigation_item(self, req):
> +        """Highlight dashboard mainnav item.
> +        """
> +        return 'dashboard'
> +
> +    def get_navigation_items(self, req):
> +        """Add an item in mainnav to access global dashboard
> +        """
> +        if 'DASHBOARD_VIEW' in req.perm:
> +            yield ('mainnav', 'dashboard',
> +                    tag.a(_(self.mainnav_label), href=req.href.dashboard()))
> +
> +    # ITemplateProvider methods
> +    def get_htdocs_dirs(self):
> +        """List `htdocs` dirs for dashboard and widgets.
> +        """
> +        resource_filename = pkg_resources.resource_filename
> +        return [
> +                 ('dashboard', resource_filename('bhdashboard', 'htdocs')),
> +                 #('widgets', resource_filename('bhdashboard.widgets', 'htdocs'))
> +                 ('layouts', resource_filename('bhdashboard.layouts', 'htdocs'))
> +                 ]
> +
> +    def get_templates_dirs(self):
> +        """List `templates` folders for dashboard and widgets.
> +        """
> +        resource_filename = pkg_resources.resource_filename
> +        return [resource_filename('bhdashboard.layouts', 'templates'),
> +                resource_filename('bhdashboard.web_ui', 'templates'),
> +                resource_filename('bhdashboard.widgets', 'templates')]
> +
> +    # Temp vars
> +    DASHBOARD_SCHEMA = {
> +            'div' : [
> +                    {
> +                        '_class' : 'row',
> +                        'div' : [
> +                                {
> +                                    '_class' : 'span8',
> +                                    'widgets' : [0]
> +                                },
> +                                {
> +                                    '_class' : 'span4',
> +                                    'widgets' : [1]
> +                                }
> +                            ]
> +                    }
> +                ],
> +            'widgets' : [
> +                    {
> +                        'args' : ['Container', None,
> +                                {'args' : {'layout' : 'bootstrap_btnbar',
> +                                        'schema' : '''
> +                                        {
> +                                          "toolbar" : [
> +                                              ["Products", null],
> +                                              ["My Tickets", 2],
> +                                              ["All tickets", 1],
> +                                              ["|", null],
> +                                              ["Projects", null],
> +                                              ["Components", 0]
> +                                            ],
> +                                          "active" : 1,
> +                                          "widgets" : [
> +                                            {
> +                                              "args" : [
> +                                                  "TicketFieldCloud",
> +                                                  null,
> +                                                  {"args" : {
> +                                                      "field" : "component",
> +                                                      "verbose" : true}}]
> +                                            },
> +                                            {
> +                                              "args" : [
> +                                                  "TicketQuery", null,
> +                                                  {"args" : {
> +                                                      "max" : 10,
> +                                                      "query" : "''' +
> +                'status!=closed&group=time&col=id&col=summary&col=owner' \
> +                '&col=status&col=priority&order=priority&groupdesc=1&desc=1' +
> +                                                      '''",
> +                                                      "title" : "All Tickets"}
> +                                                  }],
> +                                              "altlinks" : false
> +                                            },
> +                                            {
> +                                              "args" : [
> +                                                  "TicketQuery", null,
> +                                                  {"args" : {
> +                                                      "max" : 10,
> +                                                      "query" : "''' +
> +                'status!=closed&group=time&col=id&col=summary&col=owner' \
> +                '&col=status&col=priority&order=priority&groupdesc=1&desc=1' \
> +                '&owner=$USER' +
> +                                                      '''",
> +                                                      "title" : "My Tickets"}
> +                                                  }],
> +                                              "altlinks" : false
> +                                            }
> +                                          ]
> +                                        }
> +                                        ''',
> +                                        'title' : _("Dashboard")
> +                                        }
> +                                }]
> +                    },
> +                    {
> +                        'args' : ['Timeline', None, {'args' : {}}]
> +                    },
> +                ]
> +        }
> +
> +    # Public API
> +    def expand_layout_data(self, req, layout_name, schema):
> +        """Determine the template needed to render a specific layout
> +        and the data needed to place the widgets at expected
> +        location.
> +        """
> +        layout = DashboardSystem(self.env).resolve_layout(layout_name)
> +
> +        ctx = Context.from_request(req)
> +        template = layout.expand_layout(layout_name, ctx, {
> +                'schema' : schema
> +            })['template']
> +        return template, schema
> +
> +    def expand_widget_data(self, req, schema):
> +        """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]))
> +        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()))
> +        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'],
> +                '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)]
> +
> +class DashboardChrome:
> +    """Helper functions providing access to dashboard infrastructure
> +    in Genshi templates. Useful to reuse layouts and widgets across
> +    website.
> +    """
> +    def __init__(self, env):
> +        self.env = env
> +
> +    def embed_layout(self, context, **kwargs):
> +        """Render layout and widgets
> +
> +        :param context: Rendering context
> +        :param layout: Identifier of target layout
> +        :param schema: Data describing widget positioning
> +        :param widgets: Widgets definition
> +        """
> +        dbmod = DashboardModule(self.env)
> +        raise NotImplementedError("DashboardChrome.embed_layout")
> +
> +    def expand_widget(self, context, widget):
> +        """Render single widget
> +
> +        :param context: Rendering context
> +        :param widget: Widget definition
> +        """
> +        dbmod = DashboardModule(self.env)
> +        if isinstance(widget['args'], basestring):
> +            widgets['args'] = json.loads(widget['args'])
> +        return dbmod.expand_widget_data(
> +                    context.req,
> +                    {'widgets' : [widget]}
> +                )[0]
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/__init__.py
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> Added: 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=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,28 @@
> +<!DOCTYPE html
> +    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
> +    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml"
> +      xmlns:py="http://genshi.edgewall.org/"
> +      xmlns:bh="http://issues.apache.org/bloodhound/wiki/Ui/Dashboard"
> +      xmlns:xi="http://www.w3.org/2001/XInclude">
> +<xi:include href="layout.html" />
> +<xi:include href="widget_macros.html" />
> +<head>
> +<title>Milestone ${milestone.name}</title>
> +<link py:if="'MILESTONE_MODIFY' in perm(milestone.resource)" rel="alternate" type="application/x-wiki"
> +          title="Edit this milestone" href="${href.milestone(milestone.name, action='edit')}" />
> +</head>
> +
> +<body>
> +<div class="row">
> +<div class="span8">
> +<div class="alert">
> +<span class="label label-warning">TODO</span>  Include milestone data.
> +</div>
> +</div>
> +<div class="span4">
> +<bh:widget type="Timeline" />
> +</div>
> +</div>
> +</body>
> +</html>
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/templates/bhmilestone.html
> ------------------------------------------------------------------------------
>      svn:mime-type = text/html
>
> Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py?rev=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,25 @@
> +#!/usr/bin/env python
> +# -*- coding: UTF-8 -*-
> +
> +#  Licensed to the Apache Software Foundation (ASF) under one
> +#  or more contributor license agreements.  See the NOTICE file
> +#  distributed with this work for additional information
> +#  regarding copyright ownership.  The ASF licenses this file
> +#  to you under the Apache License, Version 2.0 (the
> +#  "License"); you may not use this file except in compliance
> +#  with the License.  You may obtain a copy of the License at
> +#
> +#   http://www.apache.org/licenses/LICENSE-2.0
> +#
> +#  Unless required by applicable law or agreed to in writing,
> +#  software distributed under the License is distributed on an
> +#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
> +#  KIND, either express or implied.  See the License for the
> +#  specific language governing permissions and limitations
> +#  under the License.
> +
> +
> +r"""Project dashboard for Apache(TM) Bloodhound
> +
> +Overriden version of Trac ticket interface
> +"""
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/__init__.py
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py?rev=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,61 @@
> +#!/usr/bin/env python
> +# -*- coding: UTF-8 -*-
> +
> +#  Licensed to the Apache Software Foundation (ASF) under one
> +#  or more contributor license agreements.  See the NOTICE file
> +#  distributed with this work for additional information
> +#  regarding copyright ownership.  The ASF licenses this file
> +#  to you under the Apache License, Version 2.0 (the
> +#  "License"); you may not use this file except in compliance
> +#  with the License.  You may obtain a copy of the License at
> +#
> +#   http://www.apache.org/licenses/LICENSE-2.0
> +#
> +#  Unless required by applicable law or agreed to in writing,
> +#  software distributed under the License is distributed on an
> +#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
> +#  KIND, either express or implied.  See the License for the
> +#  specific language governing permissions and limitations
> +#  under the License.
> +
> +r"""Roadmap view for Apache(TM) Bloodhound
> +
> +Customizing roadmap user interface.
> +"""
> +
> +__metaclass__ = type
> +
> +from itertools import izip
> +import pkg_resources
> +import re
> +
> +from genshi.builder import tag
> +from trac.core import Component, implements
> +from trac.mimeview.api import Context
> +from trac.ticket.roadmap import MilestoneModule, RoadmapModule
> +from trac.util.translation import _
> +from trac.web.api import IRequestFilter
> +from trac.web.chrome import add_ctxtnav, add_stylesheet
> +
> +from bhdashboard.api import DashboardSystem
> +
> +class BloodhoundMilestoneModule(Component):
> +    """Override default milestone views.
> +    """
> +    implements(IRequestFilter)
> +
> +    # IRequestFilter methods
> +
> +    def pre_process_request(self, req, handler):
> +        """Always returns the request handler unchanged.
> +        """
> +        return handler
> +
> +    def post_process_request(self, req, template, data, content_type):
> +        """Customize milestone view.
> +        """
> +        mdl = self.env[MilestoneModule]
> +        if mdl is not None and mdl.match_request(req):
> +            return 'bhmilestone.html', data, content_type
> +        else:
> +            return template, data, content_type
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/web_ui/ticket/roadmap.py
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py?rev=1326583&r1=1326582&r2=1326583&view=diff
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py (original)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/query.py Mon Apr 16 13:00:35 2012
> @@ -32,7 +32,7 @@ from genshi.builder import tag
>   from trac.core import implements, TracError
>   from trac.mimeview.api import Context
>   from trac.resource import Resource, ResourceNotFound
> -from trac.ticket.query import QueryModule
> +from trac.ticket.query import Query, QueryModule
>   from trac.util.translation import _
>   from trac.web.api import RequestDone
>
> @@ -136,3 +136,11 @@ class TicketQueryWidget(WidgetBase):
>
>       render_widget = pretty_wrapper(render_widget, check_widget_name)
>
> +#--------------------------------------
> +# Query functions and methods
> +#--------------------------------------
> +
> +def exec_query(env, req, qstr='status!=closed'):
> +    """ Perform a ticket query, returning a list of ticket ID's.
> +    """
> +    return Query.from_string(env, qstr).execute(req)
>
> Added: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html?rev=1326583&view=auto
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html (added)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html Mon Apr 16 13:00:35 2012
> @@ -0,0 +1,8 @@
> +
> +<div class="well"
> +    xmlns="http://www.w3.org/1999/xhtml"
> +    xmlns:py="http://genshi.edgewall.org/"
> +    xmlns:xi="http://www.w3.org/2001/XInclude">
> +  $legend
> +<div class="pull-right">${'%d%%' % stats.done_percent}</div>
> +</div>
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
> ------------------------------------------------------------------------------
>      svn:eol-style = native
>
> Propchange: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/templates/widget_progress.html
> ------------------------------------------------------------------------------
>      svn:mime-type = text/html
>
> Modified: incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py?rev=1326583&r1=1326582&r2=1326583&view=diff
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py (original)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/widgets/ticket.py Mon Apr 16 13:00:35 2012
> @@ -29,13 +29,18 @@ from itertools import imap, islice
>   from genshi.builder import tag
>   from trac.core import implements, TracError
>   from trac.ticket.api import TicketSystem
> +from trac.ticket.roadmap import apply_ticket_permissions, get_ticket_stats, \
> +                            ITicketGroupStatsProvider, RoadmapModule
>   from trac.util.translation import _
> +from trac.web.chrome import add_stylesheet
>
>   from bhdashboard.api import DateField, EnumField, InvalidWidgetArgument, \
>                               ListField
> +from bhdashboard.widgets.query import exec_query
>   from bhdashboard.util import WidgetBase, check_widget_name, \
> -                              dummy_request, merge_links, minmax, \
> -                              pretty_wrapper, trac_version, trac_tags
> +                            dummy_request, merge_links, minmax, \
> +                            pretty_wrapper, resolve_ep_class, \
> +                            trac_version, trac_tags
>
>   class TicketFieldCloudWidget(WidgetBase):
>       """Display a tag cloud representing frequency of values assigned to
> @@ -116,3 +121,70 @@ class TicketFieldCloudWidget(WidgetBase)
>
>       render_widget = pretty_wrapper(render_widget, check_widget_name)
>
> +class TicketGroupStatsWidget(WidgetBase):
> +    """Display progress bar illustrating statistics gathered on a group
> +    of tickets.
> +    """
> +    def get_widget_params(self, name):
> +        """Return a dictionary containing arguments specification for
> +        the widget with specified name.
> +        """
> +        return {
> +                'query' : {
> +                        'default' : 'status!=closed',
> +                        'desc' : """Query string""",
> +                    },
> +                'stats_provider' : {
> +                        'desc' : """Name of the component implementing
> +        `ITicketGroupStatsProvider`, which is used to collect statistics
> +        on groups of tickets.""",
> +                        'default' : 'DefaultTicketGroupStatsProvider'
> +                    },
> +                'skin' : {
> +                        'desc' : """Look and feel of the progress bar""",
> +                        'type' : EnumField('info', 'success', 'warning',
> +                                'danger',
> +                                'info-stripped', 'success-stripped',
> +                                'warning-stripped', 'danger-stripped')
> +                    },
> +                'title' : {
> +                        'desc' : """Widget title""",
> +                    },
> +                'legend' : {
> +                        'desc' : """Text on top of the progress bar""",
> +                    },
> +                'desc' : {
> +                        'desc' : """Descriptive (wiki) text""",
> +                    },
> +            }
> +    get_widget_params = pretty_wrapper(get_widget_params, check_widget_name)
> +
> +    def render_widget(self, name, context, options):
> +        """Prepare ticket stats
> +        """
> +        req = context.req
> +        params = ('query', 'stats_provider', 'skin', 'title', 'legend', 'desc')
> +        qstr, pnm, skin, title, legend, desc = \
> +                self.bind_params(name, options, *params)
> +        statsp = resolve_ep_class(ITicketGroupStatsProvider, self, pnm,
> +                                    default=RoadmapModule(self.env).stats_provider)
> +        skin = (skin or '').split('-', 2)
> +        progress_css = 'progress ' + ' '.join('progress-'+c for c in skin if c)
> +
> +        tickets = exec_query(self.env, req, qstr)
> +        tickets = apply_ticket_permissions(self.env, req, tickets)
> +        stat = get_ticket_stats(self.stats_provider, tickets)
> +
> +        add_stylesheet('dashboard/bootstrap.css')
> +        return 'widget_progress.html', \
> +                {
> +                    'title' : title,
> +                    'data' : dict(
> +                            desc=desc,
> +                            legend=legend,
> +                            stats=stat,
> +                        ),
> +                }, \
> +                context
> +
> +    render_widget = pretty_wrapper(render_widget, check_widget_name)
>
> Modified: incubator/bloodhound/trunk/bloodhound_dashboard/setup.py
> URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_dashboard/setup.py?rev=1326583&r1=1326582&r2=1326583&view=diff
> ==============================================================================
> --- incubator/bloodhound/trunk/bloodhound_dashboard/setup.py (original)
> +++ incubator/bloodhound/trunk/bloodhound_dashboard/setup.py Mon Apr 16 13:00:35 2012
> @@ -102,6 +102,14 @@ PKG_INFO = {'bhdashboard' : ('bhdashboar
>                               # Package data
>                               ['templates/*', 'htdocs/*'],
>                             ),
> +            'bhdashboard.web_ui' : ('bhdashboard/web_ui',     # Package dir
> +                            # Package data
> +                            ['templates/*', 'htdocs/*'],
> +                          ),
> +            'bhdashboard.web_ui.ticket' : ('bhdashboard/web_ui/ticket',     # Package dir
> +                            # Package data
> +                            [],
> +                          ),
>               'bhdashboard.tests' : ('bhdashboard/tests',     # Package dir
>                               # Package data
>                               ['data/**'],
> @@ -113,6 +121,7 @@ ENTRY_POINTS = r"""
>                  bhdashboard.api = bhdashboard.api
>                  bhdashboard.layouts.bootstrap = bhdashboard.layouts.bootstrap
>                  bhdashboard.web_ui = bhdashboard.web_ui
> +               bhdashboard.web_ui.ticket.roadmap = bhdashboard.web_ui.ticket.roadmap
>                  bhdashboard.widgets.containers = bhdashboard.widgets.containers
>                  bhdashboard.widgets.query = bhdashboard.widgets.query
>                  bhdashboard.widgets.report = bhdashboard.widgets.report
>
>