You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by tv...@apache.org on 2012/12/18 05:29:28 UTC
[1/41] git commit: [#4957] Group multiple tools of same type in navbar
Updated Branches:
refs/heads/tv/4957 1e34809b3 -> 506a731e7 (forced update)
[#4957] Group multiple tools of same type in navbar
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/506a731e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/506a731e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/506a731e
Branch: refs/heads/tv/4957
Commit: 506a731e7f67251f58fa731f81ff8dbaf714ffd3
Parents: 0a7b185
Author: Tim Van Steenburgh <tv...@gmail.com>
Authored: Fri Dec 14 22:42:50 2012 +0000
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Tue Dec 18 04:27:01 2012 +0000
----------------------------------------------------------------------
Allura/allura/app.py | 9 +++-
Allura/allura/controllers/project.py | 11 +++++
Allura/allura/model/project.py | 37 +++++++++++++++-
Allura/allura/nf/allura/css/site_style.css | 23 ++++++++++
Allura/allura/templates/jinja_master/top_nav.html | 8 +++-
Allura/allura/templates/tool_list.html | 21 +++++++++
Allura/allura/tests/functional/test_admin.py | 14 +++---
Allura/allura/tests/functional/test_tool_list.py | 13 ++++++
Allura/allura/tests/unit/test_project.py | 30 +++++++++++++
Allura/allura/tests/unit/test_sitemapentry.py | 16 +++++++
10 files changed, 171 insertions(+), 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/app.py
----------------------------------------------------------------------
diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index 613e88d..d691bf2 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -33,7 +33,8 @@ class ConfigOption(object):
class SitemapEntry(object):
- def __init__(self, label, url=None, children=None, className=None, ui_icon=None, small=None):
+ def __init__(self, label, url=None, children=None, className=None,
+ ui_icon=None, small=None, tool_name=None):
self.label = label
self.className = className
if url is not None:
@@ -44,6 +45,8 @@ class SitemapEntry(object):
if children is None:
children = []
self.children = children
+ self.tool_name = tool_name
+ self.matching_urls = []
def __getitem__(self, x):
"""
@@ -92,6 +95,10 @@ class SitemapEntry(object):
self.children.append(e)
child_index[lbl] = e
+ def matches_url(self, request):
+ """Return true if this SitemapEntry 'matches' the url of `request`."""
+ return self.url in request.upath_info or any([
+ url in request.upath_info for url in self.matching_urls])
class Application(object):
"""
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 9c3c5c5..a420117 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -254,6 +254,16 @@ class HostNeighborhoodController(WsgiDispatchController, NeighborhoodController)
nf = NewForgeController()
search = SearchController()
+class ToolListController(object):
+ """Renders a list of all tools of a given type in the current project."""
+
+ @expose('jinja:allura:templates/tool_list.html')
+ def _default(self, tool_name, *args, **kw):
+ tool_name = tool_name.lower()
+ entries = [e for e in c.project.sitemap()
+ if e.tool_name and e.tool_name.lower() == tool_name]
+ return dict(entries=entries, type=entries[0].tool_name if entries else None)
+
class ProjectController(object):
def __init__(self):
@@ -261,6 +271,7 @@ class ProjectController(object):
setattr(self, 'feed.atom', self.feed)
setattr(self, '_nav.json', self._nav)
self.screenshot = ScreenshotsController()
+ self._list = ToolListController()
@expose('json:')
def _nav(self):
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/model/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index c3ac033..454850c 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -1,4 +1,5 @@
import logging
+from collections import Counter, OrderedDict
from datetime import datetime
from tg import config
@@ -418,7 +419,8 @@ class Project(MappedClass, ActivityNode, ActivityObject):
if app.is_visible_to(c.user):
for sm in app.main_menu():
entry = sm.bind_app(app)
- entry.ui_icon='tool-%s' % ac.tool_name.lower()
+ entry.tool_name = ac.tool_name
+ entry.ui_icon = 'tool-%s' % entry.tool_name.lower()
ordinal = int(ac.options.get('ordinal', 0)) + delta_ordinal
if ordinal > max_ordinal:
max_ordinal = ordinal
@@ -431,6 +433,39 @@ class Project(MappedClass, ActivityNode, ActivityObject):
entries = sorted(entries, key=lambda e: e['ordinal'])
return [e['entry'] for e in entries]
+ def grouped_navbar_entries(self):
+ """Return a ``allura.app.SitemapEntry`` list suitable for rendering
+ the project navbar with tools grouped together by tool type.
+ """
+ # get orginal (non-grouped) navbar entries
+ sitemap = self.sitemap()
+ # ordered dict to preserve the orginal ordering of tools
+ grouped_nav = OrderedDict()
+ # count how many tools of each type we have
+ counts = Counter([e.tool_name.lower() for e in sitemap if e.tool_name])
+ for e in sitemap:
+ # if it's not a tool, add to navbar and continue
+ if not e.tool_name:
+ grouped_nav[id(e)] = e
+ continue
+ tool_name = e.tool_name.lower()
+ # tool of a type we don't have in the navbar yet
+ if tool_name not in grouped_nav:
+ # there's more than one tool of this type
+ if counts.get(tool_name, 1) > 1:
+ # change label to be the tool name (type)
+ e.label = e.tool_name
+ # add tool url to list of urls that will match this nav entry
+ e.matching_urls.append(e.url)
+ # change url to point to tool list page
+ e.url = self.url() + '_list/' + tool_name
+ grouped_nav[tool_name] = e
+ else:
+ # already have a tool of this type in the nav; add this tool's
+ # url to the list of urls that match this nav entry
+ grouped_nav[tool_name].matching_urls.append(e.url)
+ return grouped_nav.values()
+
def parent_iter(self):
yield self
pp = self.parent_project
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/nf/allura/css/site_style.css
----------------------------------------------------------------------
diff --git a/Allura/allura/nf/allura/css/site_style.css b/Allura/allura/nf/allura/css/site_style.css
index c5dec9e..ebe842a 100644
--- a/Allura/allura/nf/allura/css/site_style.css
+++ b/Allura/allura/nf/allura/css/site_style.css
@@ -370,6 +370,29 @@ td, td img {
transform: rotate(45deg);
}
+.tool-count {
+ display: block;
+ position: absolute;
+ z-index: 2;
+ height: 13px;
+ width: 13px;
+ line-height: 13px;
+ bottom: 17px;
+ left: 64%;
+ font-size: 11px;
+ font-weight: bold;
+ color: white;
+ padding: 1px;
+ border: 1px solid #9F0000;
+ background-color: #A70000;
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ -o-border-radius: 2px;
+ -ms-border-radius: 2px;
+ -khtml-border-radius: 2px;
+ border-radius: 2px;
+}
+
.nowrap {
white-space: nowrap;
}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/templates/jinja_master/top_nav.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/jinja_master/top_nav.html b/Allura/allura/templates/jinja_master/top_nav.html
index dfa288a..0ba753d 100644
--- a/Allura/allura/templates/jinja_master/top_nav.html
+++ b/Allura/allura/templates/jinja_master/top_nav.html
@@ -1,5 +1,5 @@
{% if c.project %}
- {% for s in c.project.sitemap() %}
+ {% for s in c.project.grouped_navbar_entries() %}
<a href="{{s.url}}" class="ui-icon-{{s.ui_icon or 'admin'}}">
{{s.label}}
{% if s.label == 'Home' %}
@@ -8,10 +8,14 @@
<span class="diamond"></span>
{% endif %}
{% else %}
- {% if s.url in request.upath_info or c.project.neighborhood.url()+'_admin' in request.upath_info %}
+ {% if s.matches_url(request) or c.project.neighborhood.url()+'_admin' in request.upath_info %}
<span class="diamond"></span>
{% endif %}
{% endif %}
+ {% set grouped_tool_count = s.matching_urls|length %}
+ {% if grouped_tool_count %}
+ <span class="tool-count">{{grouped_tool_count}}</span>
+ {% endif %}
</a>
{% endfor %}
<div style="clear:both"></div>
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/templates/tool_list.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/tool_list.html b/Allura/allura/templates/tool_list.html
new file mode 100644
index 0000000..67ab8df
--- /dev/null
+++ b/Allura/allura/templates/tool_list.html
@@ -0,0 +1,21 @@
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}{{c.project.name}} / {{type}} tools{% endblock %}
+
+{% block extra_css %}
+{% endblock %}
+
+{% block inner_grid %}{% endblock %}
+{% block header_classes %} colored title{% endblock %}
+{% block header %}{{type}} tools{% endblock %}
+
+{% block content %}
+<div>
+ <ul>
+ {% for e in entries %}
+ <li><a href="{{e.url}}">{{e.label}}</a></li>
+ {% endfor %}
+ </ul>
+</div>
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 656f1c9..a049e5b 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -99,15 +99,15 @@ class TestProjectAdmin(TestController):
'new.mount_point':'test-tool2',
'new.mount_label':'Test Tool2'})
assert 'error' not in self.webflash(r)
- # check the nav - the similarly named tool should NOT be active
+ # check the nav - tools of same type are grouped
r = self.app.get('/p/test/test-tool/Home/')
- active_link = r.html.findAll('span',{'class':'diamond'})
- assert len(active_link) == 1
- assert active_link[0].parent['href'] == '/p/test/test-tool/'
- r = self.app.get('/p/test/test-tool2/Home/')
- active_link = r.html.findAll('span',{'class':'diamond'})
+ active_link = r.html.findAll('span', {'class':'diamond'})
assert len(active_link) == 1
- assert active_link[0].parent['href'] == '/p/test/test-tool2/'
+ assert active_link[0].parent['href'] == '/p/test/_list/wiki'
+ # check tool-count of grouped tools
+ tool_count = active_link[0].findNextSibling('span')['class']
+ assert tool_count['class'] == u'tool-count', tool_count['class']
+ assert tool_count.text == u'2', tool_count.text
# check can't create dup tool
r = self.app.post('/admin/update_mounts', params={
'new.install':'install',
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/tests/functional/test_tool_list.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_tool_list.py b/Allura/allura/tests/functional/test_tool_list.py
new file mode 100644
index 0000000..43810b6
--- /dev/null
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -0,0 +1,13 @@
+from allura.tests import TestController
+from allura.tests import decorators as td
+
+
+class TestToolListController(TestController):
+
+ @td.with_wiki
+ @td.with_tool('test', 'Wiki', 'wiki2')
+ def test_default(self):
+ """Test that list page contains a link to all tools of that type."""
+ r = self.app.get('/p/test/_list/wiki')
+ assert len(r.html.find('a', dict(href='/p/test/wiki/'))) == 1, r
+ assert len(r.html.find('a', dict(href='/p/test/wiki2/'))) == 1, r
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/tests/unit/test_project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
new file mode 100644
index 0000000..b59d04f
--- /dev/null
+++ b/Allura/allura/tests/unit/test_project.py
@@ -0,0 +1,30 @@
+import unittest
+from mock import Mock
+
+from allura import model as M
+from allura.app import SitemapEntry
+
+
+class TestProject(unittest.TestCase):
+ def test_grouped_navbar_entries(self):
+ p = M.Project()
+ sitemap_entries = [
+ SitemapEntry('bugs', url='bugs url', tool_name='Tickets'),
+ SitemapEntry('wiki', url='wiki url', tool_name='Wiki'),
+ SitemapEntry('discuss', url='discuss url', tool_name='Discussion'),
+ SitemapEntry('subproject', url='subproject url'),
+ SitemapEntry('features', url='features url', tool_name='Tickets'),
+ SitemapEntry('help', url='help url', tool_name='Discussion'),
+ SitemapEntry('support reqs', url='support url', tool_name='Tickets'),
+ ]
+ p.url = Mock(return_value='proj_url/')
+ p.sitemap = Mock(return_value=sitemap_entries)
+ expected = [
+ ('Tickets', 'proj_url/_list/tickets', 3),
+ ('wiki', 'wiki url', 0),
+ ('Discussion', 'proj_url/_list/discussion', 2),
+ ('subproject', 'subproject url', 0),
+ ]
+ actual = [(e.label, e.url, len(e.matching_urls))
+ for e in p.grouped_navbar_entries()]
+ self.assertEqual(expected, actual)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/506a731e/Allura/allura/tests/unit/test_sitemapentry.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
new file mode 100644
index 0000000..42fcc80
--- /dev/null
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -0,0 +1,16 @@
+import unittest
+from mock import Mock
+
+from allura.app import SitemapEntry
+
+
+class TestSitemapEntry(unittest.TestCase):
+ def test_matches_url(self):
+ request = Mock(upath_info='/p/project/tool/artifact')
+ s1 = SitemapEntry('tool', url='/p/project/tool')
+ s2 = SitemapEntry('tool2', url='/p/project/tool2')
+ s3 = SitemapEntry('Tool', url='/p/project/_list/tool')
+ s3.matching_urls.append('/p/project/tool')
+ self.assertTrue(s1.matches_url(request))
+ self.assertFalse(s2.matches_url(request))
+ self.assertTrue(s3.matches_url(request))