You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by ju...@apache.org on 2013/02/05 15:22:55 UTC

svn commit: r1442601 [1/2] - in /incubator/bloodhound/branches/bep_0003_multiproduct: ./ bloodhound_dashboard/ bloodhound_dashboard/bhdashboard/htdocs/js/ bloodhound_multiproduct/ bloodhound_search/ bloodhound_search/bhsearch/ bloodhound_search/bhsearc...

Author: jure
Date: Tue Feb  5 14:22:55 2013
New Revision: 1442601

URL: http://svn.apache.org/viewvc?rev=1442601&view=rev
Log:
Sync merge from trunk


Added:
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/bhdashboard/htdocs/js/bloodhound-stickyscroll.js
      - copied unchanged from r1442600, incubator/bloodhound/trunk/bloodhound_dashboard/bhdashboard/htdocs/js/bloodhound-stickyscroll.js
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/base.py
      - copied unchanged from r1442600, incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py
Removed:
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/utils.py
Modified:
    incubator/bloodhound/branches/bep_0003_multiproduct/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/api.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/real_index_view.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/whoosh_backend.py
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_ticket.html
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_ticket_box.html
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bloodhound_theme.html
    incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/theme.py
    incubator/bloodhound/branches/bep_0003_multiproduct/installer/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/installer/bloodhound_setup.py
    incubator/bloodhound/branches/bep_0003_multiproduct/trac/   (props changed)
    incubator/bloodhound/branches/bep_0003_multiproduct/trac/trac/web/chrome.py

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Tue Feb  5 14:22:55 2013
@@ -1 +1,7 @@
 *.DS_Store
+.idea
+.project
+.pydev
+.git
+.gitignore
+.hg

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/
------------------------------------------------------------------------------
  Merged /incubator/bloodhound/trunk:r1440985-1442600

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_dashboard/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Feb  5 14:22:55 2013
@@ -0,0 +1 @@
+*.egg-info

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Feb  5 14:22:55 2013
@@ -0,0 +1 @@
+*.egg-info

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Feb  5 14:22:55 2013
@@ -0,0 +1 @@
+*.egg-info

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/__init__.py Tue Feb  5 14:22:55 2013
@@ -35,3 +35,4 @@ except Exception, exc:
 #    raise
     msg = "Exception %s raised: '%s'" % (exc.__class__.__name__, str(exc))
 
+BHSEARCH_CONFIG_SECTION = "bhsearch"

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/api.py Tue Feb  5 14:22:55 2013
@@ -33,10 +33,6 @@ class IndexFields(object):
     AUTHOR = 'author'
     CONTENT = 'content'
     STATUS = 'status'
-    DUE = 'due'
-    COMPLETED = 'completed'
-    MILESTONE = 'milestone'
-    COMPONENT = 'component'
 
 class QueryResult(object):
     def __init__(self):
@@ -89,8 +85,14 @@ class ISearchBackend(Interface):
         Open existing index, if index does not exist, create new one
         """
 
-    def query(query, sort = None, fields = None, boost = None, filter = None,
-                  facets = None, pagenum = 1, pagelen = 20):
+    def query(
+            query,
+            sort = None,
+            fields = None,
+            filter = None,
+            facets = None,
+            pagenum = 1,
+            pagelen = 20):
         """
         Perform query implementation
 
@@ -127,10 +129,17 @@ class ISearchParticipant(Interface):
         Passes the request object to do permission checking."""
 
     def get_title():
-        """Return resource title"""
+        """Return resource title."""
 
     def get_default_facets():
-        """Return default facets for the specific resource type"""
+        """Return default facets for the specific resource type."""
+
+    def get_default_view():
+        """Return True if grid is enabled by default for specific resource."""
+
+    def get_default_view_fields(view):
+        """Return list of fields should be returned in grid by default."""
+
 
 class IQueryParser(Interface):
     """Extension point for Bloodhound Search query parser.
@@ -185,9 +194,15 @@ class BloodhoundSearchApi(Component):
 
     index_participants = ExtensionPoint(IIndexParticipant)
 
-    def query(self, query, sort = None, fields = None,
-              boost = None, filter = None,
-              facets = None, pagenum = 1, pagelen = 20):
+    def query(
+            self,
+            query,
+            sort = None,
+            fields = None,
+            filter = None,
+            facets = None,
+            pagenum = 1,
+            pagelen = 20):
         """Return query result from an underlying search backend.
 
         Arguments:
@@ -221,7 +236,6 @@ class BloodhoundSearchApi(Component):
             facets = facets,
             pagenum = pagenum,
             pagelen = pagelen,
-            boost = boost,
         )
         for query_processor in self.query_processors:
             query_processor.query_pre_process(query_parameters)

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/base.py Tue Feb  5 14:22:55 2013
@@ -29,3 +29,19 @@ class BaseIndexer(Component):
     silence_on_error = BoolOption('bhsearch', 'silence_on_error', "True",
         """If true, do not throw an exception during indexing a resource""")
 
+
+class BaseSearchParticipant(Component):
+    default_view = None
+    default_grid_fields = None
+    default_facets = None
+
+    def get_default_facets(self):
+        return self.default_facets
+
+    def get_default_view(self):
+        return self.default_view
+
+    def get_default_view_fields(self, view):
+        if view == "grid":
+            return self.default_grid_fields
+        return None

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/milestone_search.py Tue Feb  5 14:22:55 2013
@@ -19,25 +19,29 @@
 #  under the License.
 
 r"""Milestone specifics for Bloodhound Search plugin."""
-from bhsearch.api import IIndexParticipant, BloodhoundSearchApi, IndexFields, \
-    ISearchParticipant
-from bhsearch.search_resources.base import BaseIndexer
+from bhsearch import BHSEARCH_CONFIG_SECTION
+from bhsearch.api import (IIndexParticipant, BloodhoundSearchApi, IndexFields,
+    ISearchParticipant)
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
 from trac.ticket import IMilestoneChangeListener, Milestone
-from trac.config import ListOption
-from trac.core import implements, Component
+from trac.config import ListOption, Option
+from trac.core import implements
 
 MILESTONE_TYPE = u"milestone"
 
+class MilestoneFields(IndexFields):
+    DUE = "due"
+    COMPLETED = "completed"
+
 class MilestoneIndexer(BaseIndexer):
     implements(IMilestoneChangeListener, IIndexParticipant)
 
     optional_fields = {
-        'description': IndexFields.CONTENT,
-        'due': IndexFields.DUE,
-        'completed': IndexFields.COMPLETED,
+        'description': MilestoneFields.CONTENT,
+        'due': MilestoneFields.DUE,
+        'completed': MilestoneFields.COMPLETED,
     }
 
-
     # IMilestoneChangeListener methods
     def milestone_created(self, milestone):
         self._index_milestone(milestone)
@@ -110,11 +114,31 @@ class MilestoneIndexer(BaseIndexer):
         for milestone in Milestone.select(self.env, include_completed=True):
             yield self.build_doc(milestone)
 
-class MilestoneSearchParticipant(Component):
+class MilestoneSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_milestone',
-        doc="""Default facets applied to search through milestones""")
+    default_facets = []
+    default_grid_fields = [
+        MilestoneFields.ID, MilestoneFields.DUE, MilestoneFields.COMPLETED]
+    prefix = MILESTONE_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default=",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -124,9 +148,6 @@ class MilestoneSearchParticipant(Compone
     def get_title(self):
         return "Milestone"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
         #TODO: add better milestone rendering
         return u'Milestone: %s' % res['id']

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/ticket_search.py Tue Feb  5 14:22:55 2013
@@ -19,22 +19,42 @@
 #  under the License.
 
 r"""Ticket specifics for Bloodhound Search plugin."""
+from bhsearch import BHSEARCH_CONFIG_SECTION
 from bhsearch.api import ISearchParticipant, BloodhoundSearchApi, \
     IIndexParticipant, IndexFields
-from bhsearch.search_resources.base import BaseIndexer
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
 from genshi.builder import tag
 from trac.ticket.api import ITicketChangeListener
 from trac.ticket import Ticket
 from trac.ticket.query import Query
-from trac.config import ListOption
-from trac.core import implements, Component
+from trac.config import ListOption, Option
+from trac.core import implements
 
 TICKET_TYPE = u"ticket"
-TICKET_STATUS = u"status"
+
+class TicketFields(IndexFields):
+    SUMMARY = "summary"
+    MILESTONE = 'milestone'
+    COMPONENT = 'component'
+    KEYWORDS = "keywords"
+    RESOLUTION = "resolution"
+    CHANGES = 'changes'
 
 class TicketIndexer(BaseIndexer):
     implements(ITicketChangeListener, IIndexParticipant)
 
+    optional_fields = {
+        'component': TicketFields.COMPONENT,
+        'description': TicketFields.CONTENT,
+        'keywords': TicketFields.KEYWORDS,
+        'milestone': TicketFields.MILESTONE,
+        'summary': TicketFields.SUMMARY,
+        'status': TicketFields.STATUS,
+        'resolution': TicketFields.RESOLUTION,
+        'reporter': TicketFields.AUTHOR,
+    }
+
+
     #ITicketChangeListener methods
     def ticket_created(self, ticket):
         """Index a recently created ticket."""
@@ -75,28 +95,17 @@ class TicketIndexer(BaseIndexer):
     def build_doc(self, trac_doc):
         ticket = trac_doc
         doc = {
-            IndexFields.ID: unicode(ticket.id),
+            IndexFields.ID: str(ticket.id),
             IndexFields.TYPE: TICKET_TYPE,
             IndexFields.TIME: ticket.time_changed,
             }
-        fields = [
-            ('component',),
-              ('description',IndexFields.CONTENT),
-              ('keywords',),
-              ('milestone',),
-              ('summary',),
-              ('status', TICKET_STATUS),
-              ('resolution',),
-              ('reporter',IndexFields.AUTHOR),
-        ]
-        for f in fields:
-            if f[0] in ticket.values:
-                if len(f) == 1:
-                    doc[f[0]] = ticket.values[f[0]]
-                elif len(f) == 2:
-                    doc[f[1]] = ticket.values[f[0]]
-        doc['changes'] = u'\n\n'.join([x[4] for x in ticket.get_changelog()
-                                       if x[2] == u'comment'])
+
+        for field, index_field in self.optional_fields.iteritems():
+            if field in ticket.values:
+                doc[index_field] = ticket.values[field]
+
+        doc[TicketFields.CHANGES] = u'\n\n'.join(
+            [x[4] for x in ticket.get_changelog() if x[2] == u'comment'])
         return doc
 
     def get_entries_for_index(self):
@@ -111,12 +120,40 @@ class TicketIndexer(BaseIndexer):
         return query.execute()
 
 
-class TicketSearchParticipant(Component):
+class TicketSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_ticket',
-                            'status,milestone,component',
-        doc="""Default facets applied to search through tickets""")
+    default_facets = [
+        TicketFields.STATUS,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
+        ]
+    default_grid_fields = [
+        TicketFields.ID,
+        TicketFields.SUMMARY,
+        TicketFields.STATUS,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
+        ]
+    prefix = TICKET_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default = ",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -126,22 +163,19 @@ class TicketSearchParticipant(Component)
     def get_title(self):
         return "Ticket"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
-        if not TICKET_STATUS in res:
+        if not TicketFields.STATUS in res:
             stat = 'undefined_status'
             css_class = 'undefined_status'
         else:
-            css_class = res[TICKET_STATUS]
-            if res[TICKET_STATUS] == 'closed':
+            css_class = res[TicketFields.STATUS]
+            if res[TicketFields.STATUS] == 'closed':
                 resolution = ""
                 if 'resolution' in res:
                     resolution = res['resolution']
                 stat = '%s: %s' % (res['status'], resolution)
             else:
-                stat = res[TICKET_STATUS]
+                stat = res[TicketFields.STATUS]
 
         id = tag(tag.span('#'+res['id'], class_=css_class))
         return id + ': %s (%s)' % (res['summary'], stat)

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/search_resources/wiki_search.py Tue Feb  5 14:22:55 2013
@@ -19,11 +19,12 @@
 #  under the License.
 
 r"""Wiki specifics for Bloodhound Search plugin."""
-from bhsearch.api import ISearchParticipant, BloodhoundSearchApi, \
-    IIndexParticipant, IndexFields
-from bhsearch.search_resources.base import BaseIndexer
-from trac.core import implements, Component
-from trac.config import ListOption
+from bhsearch import BHSEARCH_CONFIG_SECTION
+from bhsearch.api import (ISearchParticipant, BloodhoundSearchApi,
+    IIndexParticipant, IndexFields)
+from bhsearch.search_resources.base import BaseIndexer, BaseSearchParticipant
+from trac.core import implements
+from trac.config import ListOption, Option
 from trac.wiki import IWikiChangeListener, WikiSystem, WikiPage
 
 WIKI_TYPE = u"wiki"
@@ -106,11 +107,34 @@ class WikiIndexer(BaseIndexer):
             page = WikiPage(self.env, page_name)
             yield self.build_doc(page)
 
-class WikiSearchParticipant(Component):
+class WikiSearchParticipant(BaseSearchParticipant):
     implements(ISearchParticipant)
 
-    default_facets = ListOption('bhsearch', 'default_facets_wiki',
-        doc="""Default facets applied to search through wiki pages""")
+    default_facets = []
+    default_grid_fields = [
+        IndexFields.ID,
+        IndexFields.TIME,
+        IndexFields.AUTHOR
+    ]
+    prefix = WIKI_TYPE
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        default=",".join(default_facets),
+        doc="""Default facets applied to search view of specific resource""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc = """If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default = ",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
     #ISearchParticipant members
     def get_search_filters(self, req=None):
@@ -120,9 +144,6 @@ class WikiSearchParticipant(Component):
     def get_title(self):
         return "Wiki"
 
-    def get_default_facets(self):
-        return self.default_facets
-
     def format_search_results(self, res):
         return u'%s: %s...' % (res['id'], res['content'][:50])
 

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/templates/bhsearch.html Tue Feb  5 14:22:55 2013
@@ -56,16 +56,17 @@
   <body>
     <div id="content" class="row">
 
-      <h1>This page provides prototype functionality.</h1>
+      <h1>Advanced search</h1>
       <form id="fullsearch" action="${href.bhsearch()}" method="get">
         <p>
-          <input type="text" id="q" name="q" size="40" value="${query}" />
+          <input type="text" id="q" name="q" size="60" value="${query}" />
           <!--So far, we will not support noquickjump mode for form submission-->
           <!--<input type="hidden" name="noquickjump" value="1" />-->
           <input py:if="active_type" type="hidden" name="type" value="${active_type}" />
           <py:for each="active_filter in active_filter_queries">
             <input type="hidden" name="fq" value="${active_filter.query}" />
           </py:for>
+          <input py:if="active_view" type="hidden" name="view" value="${active_view}" />
           <input type="submit" value="${_('Search')}" />
         </p>
       </form>
@@ -99,21 +100,82 @@
 
       <py:if test="results">
         <div class="span8">
-          <h2 py:if="results">
+          <h2>
             Results <small>(${results.displayed_items()})</small>
           </h2>
-          <div>
-            <dl id="results">
 
-              <py:for each="result in results">
-                <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
-                <dd class="searchable">${result.excerpt}</dd>
-                <dd>
-                  <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
-                  <span class="date">${result.date}</span>
-                </dd>
-              </py:for>
-            </dl>
+          <div class="pull-right" py:if="all_views">
+            <!--TODO: change presentation. Current implementation is very basic. -->
+            View as:
+            <py:for each="idx, view in enumerate(all_views)">
+              <b py:if="view.is_active">${view.label}</b>
+              <a href="${view.href}" py:if="not view.is_active">${view.label}</a>
+              <py:if test="idx+1!=len(all_views)"> | </py:if>
+            </py:for>
+          </div>
+
+          <div>
+            <py:if test="view=='grid'">
+              <!--todo: implement rendering in pluggable manner-->
+              <!--Rendering results in grid view-->
+              <table class="listing tickets table table-bordered table-condensed query" style="border-radius: 0px 0px 4px 4px">
+                <!--render table header-->
+                <thead>
+                  <tr class="trac-columns">
+                      <th py:for="header in headers"
+                          class="$header.name${(' desc' if header.sort=='DESC' else ' asc') if header.sort else ''}">
+                        <?python asc = _('(ascending)'); desc = _('(descending)') ?>
+                        <a title="${_('Sort by %(col)s %(direction)s', col=header.label,
+                                      direction=(desc if header.sort=='ASC' else asc))}"
+                           href="$header.href">${header.label}</a>
+                      </th>
+                  </tr>
+                </thead>
+
+                <tbody>
+                  <!--render table rows-->
+                  <py:for each="idx, result in enumerate(results)">
+                    <tr class="${'odd' if idx % 2 else 'even'} prio${result.priority_value}${
+                      ' added' if 'added' in result else ''}${
+                      ' changed' if 'changed' in result else ''}${
+                      ' removed' if 'removed' in result else ''}">
+                      <py:for each="idx, header in enumerate(headers)" py:choose="">
+                        <py:with vars="name = header.name; value = result[name];  title = _('View ')+ result['type']">
+                          <td py:when="name == 'id'" class="id"><a href="$result.href" title="${title}"
+                              class="${classes(closed=result.status == 'closed')}">#$result.id</a></td>
+                          <td py:otherwise="" class="$name" py:choose="">
+                            <a py:when="name == 'summary'" href="$result.href" title="title">$value</a>
+                            <py:when test="isinstance(value, datetime)">${pretty_dateinfo(value, dateonly=True)}</py:when>
+                            <py:when test="name == 'reporter'">${authorinfo(value)}</py:when>
+                            <py:when test="name == 'cc'">${format_emails(ticket_context, value)}</py:when>
+                            <py:when test="name == 'owner' and value">${authorinfo(value)}</py:when>
+                            <py:when test="name == 'milestone'"><a py:if="value" title="View milestone" href="${href.milestone(value)}">${value}</a></py:when>
+                            <!--<py:when test="name == 'content'">${wiki_to_oneliner(ticket_context, value)}</py:when>-->
+                            <py:when test="header.wikify">${wiki_to_oneliner(ticket_context, value)}</py:when>
+                            <py:otherwise>$value</py:otherwise>
+                          </td>
+                        </py:with>
+                      </py:for>
+                    </tr>
+                  </py:for>
+                </tbody>
+              </table>
+            </py:if>
+
+            <!--<py:if test="not headers">-->
+            <py:if test="not view">
+              <!--Rendering results in free form-->
+              <dl id="results">
+                <py:for each="result in results">
+                  <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
+                  <dd class="searchable">${result.excerpt}</dd>
+                  <dd>
+                    <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
+                    <span class="date">${result.date}</span>
+                  </dd>
+                </py:for>
+              </dl>
+            </py:if>
           </div>
 
           <xi:include py:with="paginator = results" href="bh_page_index.html" />

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/api.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/api.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/api.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/api.py Tue Feb  5 14:22:55 2013
@@ -22,7 +22,7 @@ import tempfile
 import shutil
 from bhsearch.api import BloodhoundSearchApi, ASC
 from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketSearchParticipant
 
 from bhsearch.whoosh_backend import WhooshBackend

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/index_with_whoosh.py Tue Feb  5 14:22:55 2013
@@ -22,14 +22,12 @@ import unittest
 import tempfile
 import shutil
 from bhsearch.api import BloodhoundSearchApi
-from bhsearch.milestone_search import MilestoneIndexer
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.search_resources.milestone_search import MilestoneIndexer
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketIndexer
 
 from bhsearch.whoosh_backend import WhooshBackend
-from bhsearch.search_resources.wiki_search import WikiIndexer
 from trac.test import EnvironmentStub
-from trac.ticket.api import TicketSystem
 
 
 class IndexWhooshTestCase(BaseBloodhoundSearchTest):
@@ -39,10 +37,6 @@ class IndexWhooshTestCase(BaseBloodhound
         self.whoosh_backend = WhooshBackend(self.env)
         self.whoosh_backend.recreate_index()
         self.search_api = BloodhoundSearchApi(self.env)
-        self.ticket_indexer = TicketIndexer(self.env)
-        self.wiki_indexer = WikiIndexer(self.env)
-        self.milestone_indexer = MilestoneIndexer(self.env)
-        self.ticket_system = TicketSystem(self.env)
 
     def tearDown(self):
         shutil.rmtree(self.env.path)
@@ -51,7 +45,7 @@ class IndexWhooshTestCase(BaseBloodhound
     def test_can_index_ticket(self):
         ticket = self.create_dummy_ticket()
         ticket.id = "1"
-        self.ticket_indexer.ticket_created(ticket)
+        TicketIndexer(self.env).ticket_created(ticket)
 
         results = self.search_api.query("*:*")
         self.print_result(results)
@@ -112,6 +106,7 @@ class IndexWhooshTestCase(BaseBloodhound
         self.assertEqual(2, results.hits)
 
     def test_can_reindex_milestones(self):
+        MilestoneIndexer(self.env)
         self.insert_milestone("M1")
         self.insert_milestone("M2")
         self.whoosh_backend.recreate_index()

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/milestone_search.py Tue Feb  5 14:22:55 2013
@@ -17,17 +17,14 @@
 #  KIND, either express or implied.  See the License for the
 #  specific language governing permissions and limitations
 #  under the License.
-import shutil
 import unittest
-import tempfile
 
 from bhsearch.api import BloodhoundSearchApi
-from bhsearch.search_resources.milestone_search import MilestoneSearchParticipant
-from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.search_resources.milestone_search import (
+    MilestoneSearchParticipant)
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.whoosh_backend import WhooshBackend
 
-from trac.test import EnvironmentStub
 from trac.ticket import Milestone
 
 
@@ -35,18 +32,10 @@ class MilestoneIndexerEventsTestCase(Bas
     DUMMY_MILESTONE_NAME = "dummyName"
 
     def setUp(self):
-        self.env = EnvironmentStub(enable=['bhsearch.*'])
-        self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
-        self.env.config.set('bhsearch', 'silence_on_error', "False")
+        super(MilestoneIndexerEventsTestCase, self).setUp()
         self.whoosh_backend = WhooshBackend(self.env)
         self.whoosh_backend.recreate_index()
         self.search_api = BloodhoundSearchApi(self.env)
-        self.milestone_participant = MilestoneSearchParticipant(self.env)
-        self.query_parser = DefaultQueryParser(self.env)
-
-    def tearDown(self):
-        shutil.rmtree(self.env.path)
-        self.env.reset_db()
 
     def test_can_index_created_milestone(self):
         #arrange
@@ -132,10 +121,32 @@ class MilestoneIndexerEventsTestCase(Bas
         self.assertEqual(self.DUMMY_MILESTONE_NAME, doc["id"])
         self.assertEqual("milestone", doc["type"])
 
+class MilestoneSearchParticipantTestCase(BaseBloodhoundSearchTest):
+    def setUp(self):
+        super(MilestoneSearchParticipantTestCase, self).setUp()
+        self.milestone_search = MilestoneSearchParticipant(self.env)
+
+    def test_can_get_default_grid_fields(self):
+        grid_fields = self.milestone_search.get_default_view_fields("grid")
+        print grid_fields
+        self.assertGreater(len(grid_fields), 0)
+
+    def test_can_get_default_facets(self):
+        default_facets = self.milestone_search.get_default_facets()
+        print default_facets
+        self.assertIsNotNone(default_facets)
+
+    def test_can_get_is_grid_view_defaults(self):
+        default_grid_fields = self.milestone_search.get_default_view_fields(
+            "grid")
+        print default_grid_fields
+        self.assertIsNotNone(default_grid_fields)
 
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(MilestoneIndexerEventsTestCase, 'test'))
+    suite.addTest(
+        unittest.makeSuite(MilestoneSearchParticipantTestCase, 'test'))
     return suite
 
 if __name__ == '__main__':

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/real_index_view.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/real_index_view.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/real_index_view.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/real_index_view.py Tue Feb  5 14:22:55 2013
@@ -20,7 +20,7 @@
 import unittest
 from bhsearch.web_ui import RequestParameters
 import os
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 
 from bhsearch.whoosh_backend import WhooshBackend
 from trac.test import EnvironmentStub, Mock, MockPerm

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/ticket_search.py Tue Feb  5 14:22:55 2013
@@ -20,7 +20,7 @@
 import unittest
 import tempfile
 
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.search_resources.ticket_search import TicketIndexer
 
 from trac.test import EnvironmentStub

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/web_ui.py Tue Feb  5 14:22:55 2013
@@ -23,7 +23,7 @@ import shutil
 
 from urllib import urlencode, unquote
 
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
+from bhsearch.tests.base import BaseBloodhoundSearchTest
 from bhsearch.web_ui import RequestParameters
 from bhsearch.whoosh_backend import WhooshBackend
 
@@ -39,9 +39,7 @@ DEFAULT_DOCS_PER_PAGE = 10
 
 class WebUiTestCaseWithWhoosh(BaseBloodhoundSearchTest):
     def setUp(self):
-        self.env = EnvironmentStub(enable=['trac.*', 'bhsearch.*'])
-        self.env.path = tempfile.mkdtemp('bhsearch-tempenv')
-
+        super(WebUiTestCaseWithWhoosh, self).setUp(['trac.*', 'bhsearch.*'])
         whoosh_backend = WhooshBackend(self.env)
         whoosh_backend.recreate_index()
 
@@ -62,10 +60,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.redirect_permanent = permanent
         raise RequestDone
 
-    def tearDown(self):
-        shutil.rmtree(self.env.path)
-        self.env.reset_db()
-
     def test_can_process_empty_request(self):
         data = self.process_request()
         self.assertEqual("", data["query"])
@@ -441,6 +435,86 @@ class WebUiTestCaseWithWhoosh(BaseBloodh
         self.assertEqual('T1 (new)', quick_jump_data["description"])
         self.assertEqual('/main/ticket/1', quick_jump_data["href"])
 
+
+    def test_that_ticket_search_can_return_in_grid(self):
+        #arrange
+        self.env.config.set(
+            'bhsearch',
+            'ticket_is_grid_view_default',
+            'True')
+        self.env.config.set(
+            'bhsearch',
+            'ticket_default_grid_fields',
+            'id,status,milestone,component')
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.TYPE] = "ticket"
+        self.req.args[RequestParameters.VIEW] = "grid"
+        data = self.process_request()
+        #assert
+        grid_data = data["headers"]
+        self.assertIsNotNone(grid_data)
+        fields = [column["name"] for column in grid_data]
+        self.assertEquals(["id", "status", "milestone", "component"], fields)
+
+    def test_that_grid_is_switched_off_by_default(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("headers", data)
+        self.assertNotIn("view", data)
+
+    def test_that_grid_is_switched_off_by_default_for_ticket(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.TYPE] = "ticket"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("headers", data)
+        self.assertNotIn("view", data)
+
+
+    def test_can_returns_all_views(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        all_views = data["all_views"]
+        free_view = all_views[0]
+        self.assertTrue(free_view["is_active"])
+        self.assertNotIn("view=", free_view["href"])
+        grid = all_views[1]
+        self.assertFalse(grid["is_active"])
+        self.assertIn("view=grid", grid["href"])
+
+    def test_that_active_view_is_not_set_if_not_requested(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        data = self.process_request()
+        #assert
+        self.assertNotIn("active_view", data)
+
+    def test_that_active_view_is_set_if_requested(self):
+        #arrange
+        self.insert_ticket("T1", component="c1", status="new", milestone="A")
+        #act
+        self.req.args[RequestParameters.QUERY] = "*"
+        self.req.args[RequestParameters.VIEW] = "grid"
+        data = self.process_request()
+        #assert
+        self.assertEqual("grid", data["active_view"])
+
+
     def _count_parameter_in_url(self, url, parameter_name, value):
         parameter_to_find = (parameter_name, value)
         parsed_parameters = parse_arg_list(url)

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/whoosh_backend.py Tue Feb  5 14:22:55 2013
@@ -24,9 +24,9 @@ import tempfile
 import shutil
 from bhsearch.api import ASC, DESC, SCORE
 from bhsearch.query_parser import DefaultQueryParser
-from bhsearch.tests.utils import BaseBloodhoundSearchTest
-from bhsearch.whoosh_backend import WhooshBackend, \
-    WhooshEmptyFacetErrorWorkaround
+from bhsearch.tests.base import BaseBloodhoundSearchTest
+from bhsearch.whoosh_backend import (WhooshBackend,
+    WhooshEmptyFacetErrorWorkaround)
 from trac.test import EnvironmentStub
 from trac.util.datefmt import FixedOffset, utc
 from whoosh import index, sorting, query

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/tests/wiki_search.py Tue Feb  5 14:22:55 2013
@@ -25,7 +25,8 @@ from bhsearch.api import BloodhoundSearc
 from bhsearch.query_parser import DefaultQueryParser
 from bhsearch.tests.utils import BaseBloodhoundSearchTest
 from bhsearch.whoosh_backend import WhooshBackend
-from bhsearch.search_resources.wiki_search import WikiIndexer, WikiSearchParticipant
+from bhsearch.search_resources.wiki_search import (
+    WikiIndexer, WikiSearchParticipant)
 
 from trac.test import EnvironmentStub
 from trac.wiki import WikiSystem, WikiPage

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/web_ui.py Tue Feb  5 14:22:55 2013
@@ -22,28 +22,29 @@ r"""Bloodhound Search user interface."""
 import copy
 
 import pkg_resources
+from bhsearch import BHSEARCH_CONFIG_SECTION
 import re
 
 from trac.core import Component, implements, TracError
 from genshi.builder import tag
 from trac.perm import IPermissionRequestor
 from trac.search import shorten_result
-from trac.config import OrderedExtensionsOption, ListOption
+from trac.config import OrderedExtensionsOption, ListOption, Option
 from trac.util.presentation import Paginator
 from trac.util.datefmt import format_datetime, user_time
 from trac.web import IRequestHandler
 from trac.util.translation import _
 from trac.util.html import find_element
 from trac.web.chrome import (INavigationContributor, ITemplateProvider,
-    add_link, add_stylesheet, web_context)
+                             add_link, add_stylesheet, web_context)
 from bhsearch.api import (BloodhoundSearchApi, ISearchParticipant, SCORE, ASC,
-    DESC, IndexFields)
+                          DESC, IndexFields)
 from trac.wiki.formatter import extract_link
 
 SEARCH_PERMISSION = 'SEARCH_VIEW'
 DEFAULT_RESULTS_PER_PAGE = 10
 DEFAULT_SORT = [(SCORE, ASC), ("time", DESC)]
-
+#VIEW_GRID = "grid"
 
 class RequestParameters(object):
     """
@@ -57,21 +58,23 @@ class RequestParameters(object):
     NO_QUICK_JUMP = "noquickjump"
     PAGELEN = "pagelen"
     FILTER_QUERY = "fq"
+    VIEW = "view"
 
     def __init__(self, req):
         self.req = req
 
-        self.query = req.args.getfirst(self.QUERY)
-        if self.query is None:
+        self.original_query = req.args.getfirst(self.QUERY)
+        if self.original_query is None:
             self.query = ""
         else:
-            self.query = self.query.strip()
+            self.query = self.original_query.strip()
 
         self.no_quick_jump = int(req.args.getfirst(self.NO_QUICK_JUMP, '0'))
 
+        self.view = req.args.getfirst(self.VIEW)
         self.filter_queries = req.args.getlist(self.FILTER_QUERY)
         self.filter_queries = self._remove_possible_duplications(
-                        self.filter_queries)
+            self.filter_queries)
 
         #TODO: retrieve sort from query string
         self.sort = DEFAULT_SORT
@@ -86,11 +89,12 @@ class RequestParameters(object):
             RequestParameters.FILTER_QUERY: []
         }
 
+        if self.original_query is not None:
+            self.params[self.QUERY] = self.original_query
         if self.no_quick_jump > 0:
             self.params[self.NO_QUICK_JUMP] = self.no_quick_jump
-
-        if self.query:
-            self.params[self.QUERY] = self.query
+        if self.view is not None:
+            self.params[self.VIEW] = self.view
         if self.pagelen != DEFAULT_RESULTS_PER_PAGE:
             self.params[self.PAGELEN] = self.pagelen
         if self.page > 1:
@@ -107,31 +111,36 @@ class RequestParameters(object):
 
     def create_href(
             self,
-            page = None,
+            page=None,
             type=None,
-            skip_type = False,
-            skip_page = False,
-            additional_filter = None,
-            force_filters = None,
-            ):
+            skip_type=False,
+            skip_page=False,
+            additional_filter=None,
+            force_filters=None,
+            view=None,
+            skip_view=False,
+    ):
         params = copy.deepcopy(self.params)
 
         #noquickjump parameter should be always set to 1 for urls
         params[self.NO_QUICK_JUMP] = 1
 
-        if page:
+        if skip_view:
+            self._delete_if_exists(params, self.VIEW)
+        elif view:
+            params[self.VIEW] = view
+
+        if skip_page:
+            self._delete_if_exists(params, self.PAGE)
+        elif page:
             params[self.PAGE] = page
 
-        if skip_page and self.PAGE in params:
-            del(params[self.PAGE])
-
-        if type:
+        if skip_type:
+            self._delete_if_exists(params, self.TYPE)
+        elif type:
             params[self.TYPE] = type
 
-        if skip_type and self.TYPE in params:
-            del(params[self.TYPE])
-
-        if additional_filter and \
+        if additional_filter and\
            additional_filter not in params[self.FILTER_QUERY]:
             params[self.FILTER_QUERY].append(additional_filter)
         elif force_filters is not None:
@@ -139,15 +148,33 @@ class RequestParameters(object):
 
         return self.req.href.bhsearch(**params)
 
+    def _delete_if_exists(self, params, name):
+        if name in params:
+            del params[name]
+
     def is_show_all_mode(self):
         return self.type is None
 
+
 class BloodhoundSearchModule(Component):
     """Main search page"""
+    DATA_HEADERS = "headers"
+    DATA_ALL_VIEWS = "all_views"
+    DATA_ACTIVE_VIEW = "active_view"
+    DATA_VIEW = "view"
+    DATA_VIEW_GRID = "grid"
+    #bhsearch may support more pluggable views later
+    VIEWS_SUPPORTED = {
+        None: "Free text",
+        DATA_VIEW_GRID: "Grid"
+    }
+    VIEWS_WITH_KNOWN_FIELDS = [DATA_VIEW_GRID]
+
+    OBLIGATORY_FIELDS_TO_SELECT = [IndexFields.ID, IndexFields.TYPE]
 
     implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
-               ITemplateProvider,
-    #           IWikiSyntaxProvider #todo: implement later
+        ITemplateProvider,
+        #           IWikiSyntaxProvider #todo: implement later
     )
 
     search_participants = OrderedExtensionsOption(
@@ -157,9 +184,31 @@ class BloodhoundSearchModule(Component):
         "TicketSearchParticipant, WikiSearchParticipant"
     )
 
-
-    default_facets_all = ListOption('bhsearch', 'default_facets_all',
-        doc="""Default facets applied to search through all resources""")
+    prefix = "all"
+    default_grid_fields = [
+        IndexFields.ID,
+        IndexFields.TYPE,
+        IndexFields.TIME,
+        IndexFields.AUTHOR,
+        IndexFields.CONTENT,
+    ]
+
+    default_facets = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_facets',
+        doc="""Default facets applied to search view of all resources""")
+
+    default_view = Option(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_view',
+        doc="""If true, show grid as default view for specific resource in
+            Bloodhound Search results""")
+
+    default_grid_fields = ListOption(
+        BHSEARCH_CONFIG_SECTION,
+        prefix + '_default_grid_fields',
+        default=",".join(default_grid_fields),
+        doc="""Default fields for grid view for specific resource""")
 
 
     # INavigationContributor methods
@@ -186,44 +235,46 @@ class BloodhoundSearchModule(Component):
         allowed_participants = self._get_allowed_participants(req)
         data = {
             'query': parameters.query,
-            }
+        }
         self._prepare_allowed_types(allowed_participants, parameters, data)
         self._prepare_active_filter_queries(
             parameters,
             data,
         )
-        working_query_string = parameters.query.strip()
-
 
         #TBD: should search return results on empty query?
-#        if not any((
-#            working_query_string,
-#            parameters.type,
-#            parameters.filter_queries,
-#            )):
-#            return self._return_data(req, data)
+        #        if not any((
+        #            query,
+        #            parameters.type,
+        #            parameters.filter_queries,
+        #            )):
+        #            return self._return_data(req, data)
 
         self._prepare_quick_jump(
             parameters,
-            working_query_string,
             data)
+        fields = self._prepare_fields_and_view(
+            allowed_participants, parameters, data)
 
         query_filter = self._prepare_query_filter(
-            parameters,
-            allowed_participants)
+            parameters, allowed_participants)
 
         facets = self._prepare_facets(parameters, allowed_participants)
 
         query_system = BloodhoundSearchApi(self.env)
         query_result = query_system.query(
-            working_query_string,
+            parameters.query,
             pagenum=parameters.page,
             pagelen=parameters.pagelen,
             sort=parameters.sort,
+            fields=fields,
             facets=facets,
-            filter=query_filter)
+            filter=query_filter,
+        )
 
-        ui_docs = [self._process_doc(doc, req, allowed_participants)
+        is_free_text_view = (self.DATA_VIEW not in data)
+        ui_docs = [self._process_doc(
+                        doc, req, allowed_participants, is_free_text_view)
                    for doc in query_result.docs]
 
         results = Paginator(
@@ -234,22 +285,21 @@ class BloodhoundSearchModule(Component):
 
         results.shown_pages = self._prepare_shown_pages(
             parameters,
-            shown_pages = results.get_shown_pages(parameters.pagelen))
+            shown_pages=results.get_shown_pages(parameters.pagelen))
 
         results.current_page = {'href': None,
                                 'class': 'current',
                                 'string': str(results.page + 1),
-                                'title':None}
+                                'title': None}
 
         if results.has_next_page:
-            next_href = parameters.create_href(page = parameters.page + 1)
+            next_href = parameters.create_href(page=parameters.page + 1)
             add_link(req, 'next', next_href, _('Next Page'))
 
         if results.has_previous_page:
-            prev_href = parameters.create_href(page = parameters.page - 1)
+            prev_href = parameters.create_href(page=parameters.page - 1)
             add_link(req, 'prev', prev_href, _('Previous Page'))
 
-
         data['results'] = results
 
         self._prepare_result_facet_counts(
@@ -261,15 +311,89 @@ class BloodhoundSearchModule(Component):
         data['page_href'] = parameters.create_href()
         return self._return_data(req, data)
 
+
+    def _prepare_fields_and_view(self, allowed_participants, parameters, data):
+        self._add_views_selector(parameters, data)
+        active_participant = self._get_active_participant(
+            parameters, allowed_participants)
+        view = self._get_view(parameters, active_participant)
+        if view:
+            data[self.DATA_VIEW] = view
+        fields_to_select = None
+        if view in self.VIEWS_WITH_KNOWN_FIELDS:
+            if active_participant:
+                fields_in_view = active_participant.get_default_view_fields(
+                    view)
+            elif view == self.DATA_VIEW_GRID:
+                fields_in_view = self.default_grid_fields
+            else:
+                raise TracError("Unsupported view: %s" % view)
+            data[self.DATA_HEADERS] = [self._create_headers_item(field)
+                                        for field in fields_in_view]
+            fields_to_select = self._optionally_add_obligatory_fields(
+                fields_in_view)
+        return fields_to_select
+
+    def _add_views_selector(self, parameters, data):
+        active_view = parameters.view
+        all_views = []
+        for view, label in self.VIEWS_SUPPORTED.iteritems():
+            all_views.append(dict(
+                label=_(label),
+                href=parameters.create_href(
+                    view=view, skip_view=(view is None)),
+                is_active = (view == active_view)
+            ))
+        data[self.DATA_ALL_VIEWS] = all_views
+        if parameters.view:
+            data[self.DATA_ACTIVE_VIEW] = parameters.view
+
+    def _optionally_add_obligatory_fields(self, fields_in_view):
+        fields_to_select = list(fields_in_view)
+        for obligatory_field in self.OBLIGATORY_FIELDS_TO_SELECT:
+            if obligatory_field is not fields_to_select:
+                fields_to_select.append(obligatory_field)
+        return fields_to_select
+
+    def _create_headers_item(self, field):
+        return dict(
+            name=field,
+            href="",
+            #TODO:add translated column label. Now it is really temporary
+            #workaround
+            label=field,
+            sort=None,
+        )
+
+    def _get_view(self, parameters, active_participant):
+        view = parameters.view
+        if view is None:
+            if active_participant is not None:
+                view = active_participant.get_default_view()
+            else:
+                view = self.default_view
+        if view is not None:
+            view =  view.strip().lower()
+        return view
+
+
+    def _get_active_participant(self, parameters, allowed_participants):
+        active_type = parameters.type
+        if active_type is not None and \
+           active_type in allowed_participants:
+            return allowed_participants[active_type]
+        else:
+            return None
+
+
     def _prepare_quick_jump(self,
                             parameters,
-                            working_query_string,
                             data):
-        if not working_query_string:
+        if not parameters.query:
             return
         check_result = self._check_quickjump(
             parameters.req,
-            working_query_string)
+            parameters.query)
         if check_result:
             data["quickjump"] = check_result
 
@@ -301,7 +425,6 @@ class BloodhoundSearchModule(Component):
                 req.redirect(quickjump_href)
 
 
-
     def _prepare_allowed_types(self, allowed_participants, parameters, data):
         active_type = parameters.type
         if active_type and active_type not in allowed_participants:
@@ -327,16 +450,15 @@ class BloodhoundSearchModule(Component):
                 type = participant_with_type[participant]
                 allowed_types.append(dict(
                     label=_(participant.get_title()),
-                    active=(type ==active_type),
+                    active=(type == active_type),
                     href=parameters.create_href(
                         type=type,
                         skip_page=True,
                         force_filters=[],
                     ),
                 ))
-        data["types"] =  allowed_types
-        data["active_type"] =  active_type
-
+        data["types"] = allowed_types
+        data["active_type"] = active_type
 
 
     def _prepare_active_filter_queries(
@@ -393,8 +515,8 @@ class BloodhoundSearchModule(Component):
                         href = parameters.create_href(
                             skip_page=True,
                             additional_filter=self._create_term_expression(
-                                    field,
-                                    field_value)
+                                field,
+                                field_value)
                         )
                     per_field_dict[field_value] = dict(
                         count=count,
@@ -427,7 +549,7 @@ class BloodhoundSearchModule(Component):
         #TODO: add possibility of specifying facets in query parameters
         if parameters.is_show_all_mode():
             facets = [IndexFields.TYPE]
-            facets.extend(self.default_facets_all)
+            facets.extend(self.default_facets)
         else:
             type_participant = allowed_participants[parameters.type]
             facets = type_participant.get_default_facets()
@@ -445,10 +567,8 @@ class BloodhoundSearchModule(Component):
         add_stylesheet(req, 'common/css/search.css')
         return 'bhsearch.html', data, None
 
-    def _process_doc(self, doc, req, allowed_participants):
-        #todo: introduce copy by predefined value
+    def _process_doc(self, doc, req, allowed_participants, is_free_text_view):
         ui_doc = dict(doc)
-
         ui_doc["href"] = req.href(doc['type'], doc['id'])
         #todo: perform content adaptation here
         if doc.has_key('content'):
@@ -456,8 +576,9 @@ class BloodhoundSearchModule(Component):
         if doc.has_key('time'):
             ui_doc['date'] = user_time(req, format_datetime, doc['time'])
 
-        ui_doc['title'] = allowed_participants[doc['type']]\
-                .format_search_results(doc)
+        if is_free_text_view:
+            ui_doc['title'] = allowed_participants[
+                              doc['type']].format_search_results(doc)
         return ui_doc
 
     def _prepare_shown_pages(self, parameters, shown_pages):
@@ -465,7 +586,7 @@ class BloodhoundSearchModule(Component):
         for shown_page in shown_pages:
             page_href = parameters.create_href(page=shown_page)
             page_data.append([page_href, None, str(shown_page),
-                             'page ' + str(shown_page)])
+                              'page ' + str(shown_page)])
         fields = ['href', 'class', 'string', 'title']
         result_shown_pages = [dict(zip(fields, p)) for p in page_data]
         return result_shown_pages
@@ -473,8 +594,8 @@ class BloodhoundSearchModule(Component):
 
     # ITemplateProvider methods
     def get_htdocs_dirs(self):
-#        return [('bhsearch',
-#                 pkg_resources.resource_filename(__name__, 'htdocs'))]
+    #        return [('bhsearch',
+    #                 pkg_resources.resource_filename(__name__, 'htdocs'))]
         return []
 
     def get_templates_dirs(self):

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/whoosh_backend.py
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/whoosh_backend.py?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/whoosh_backend.py (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_search/bhsearch/whoosh_backend.py Tue Feb  5 14:22:55 2013
@@ -23,6 +23,7 @@ from bhsearch.api import ISearchBackend,
     IDocIndexPreprocessor, IResultPostprocessor, IndexFields, \
     IQueryPreprocessor
 import os
+from bhsearch.search_resources.ticket_search import TicketFields
 from trac.core import Component, implements, TracError
 from trac.config import Option
 from trac.util.text import empty
@@ -162,7 +163,6 @@ class WhooshBackend(Component):
               query,
               sort = None,
               fields = None,
-              boost = None,
               filter = None,
               facets = None,
               pagenum = 1,
@@ -359,8 +359,8 @@ class WhooshEmptyFacetErrorWorkaround(Co
 
     should_not_be_empty_fields = [
         IndexFields.STATUS,
-        IndexFields.MILESTONE,
-        IndexFields.COMPONENT,
+        TicketFields.MILESTONE,
+        TicketFields.COMPONENT,
     ]
 
     #IDocIndexPreprocessor methods

Propchange: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Feb  5 14:22:55 2013
@@ -0,0 +1 @@
+*.egg-info

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/htdocs/bloodhound.css Tue Feb  5 14:22:55 2013
@@ -17,8 +17,29 @@
   under the License.
 */
 
+body {
+  background-color: rgb(92%, 91%, 88%);
+}
+
+.container:first-child {
+  background-color: rgb(100%, 100%, 100%);
+  padding: 5px 20px;
+  border-left: 1px solid rgb(70%, 70%, 70%);
+  border-right: 1px solid rgb(70%, 70%, 70%);
+  border-bottom: 1px solid rgb(70%, 70%, 70%);
+  box-shadow: 0px 0px 10px 2px rgba(50%, 50%, 50%, 0.4);
+  border-bottom-left-radius: 7px;
+  border-bottom-right-radius: 7px;
+}
+
+#searchbox {
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
 #mainsearch {
-  margin-bottom: 0px;
+  margin-bottom: 0;
+  display: inline-block;
 }
 
 #mainnav {
@@ -134,9 +155,9 @@ div.reports form {
 
 /* @end */
 
-#ticket table.properties td,
-#ticket table.properties th {
-	font-size: 100%;
+#ticket .properties table td,
+#ticket .properties table th {
+  border: none;
 }
 
 #ticket footer {
@@ -144,6 +165,14 @@ div.reports form {
 
 }
 
+#trac-ticket-title {
+  margin-bottom: 5px;
+}
+
+#trac-ticket-title span.ticket-info {
+  font-weight: normal;
+}
+
 #activityfeed img {
   display: none;
 }
@@ -156,6 +185,11 @@ div.reports form {
   color: grey;
 }
 
+#mobile-activity {
+  margin-top: 50px;
+}
+
+
 div.reports form,
 div.reports .inlinebutton {
   float: right;
@@ -172,12 +206,6 @@ div.reports h2 {
 
 /* @group Wiki */
 
-/*
-textarea.wikitext {
-  min-width: 66%;
-}
-*/
-
 .wiki-toc {
   float: right;
   margin-left: 25px;
@@ -202,33 +230,43 @@ pre.wiki {
 
 /* @group Ticket page modifications */
 
-#comment {
-  width: 605px;
+.bh-ticket-buttons {
+  padding-left: 5px;
 }
 
-#field-description {
-  width: 505px;
+.clearboth {
+  clear: both;
 }
 
-#qct-fieldset #field-description {
-  width: auto;
+.ticket .keywords {
+  margin: 20px 0;
 }
 
-.bh-ticket-buttons {
-  padding-left: 5px;
+.ticket .properties h5 {
+  margin-top: 0;
+  margin-bottom: 0;
 }
 
-.ticket .properties .enum h5, #basic-properties h5 {
-  margin-top: 0px;
+.ticket-box-field, .ticket-box-info {
+  margin-top: 0;
+  margin-bottom: 0;
 }
 
-.ownership {
-  margin-left: 25px;
+.ticket-box-info dt {
+  text-align: left;
 }
 
-.ticket textarea {
-  height: auto;
-  width: auto;
+#modifyproperties th, #modifyproperties td {
+  vertical-align: baseline;
+  border: none;
+}
+
+.ticket-summary #field-summary {
+  margin-bottom: 0;
+}
+
+.ticket .properties .enum h5 {
+  margin-top: 0px;
 }
 
 .trac-loading {
@@ -259,6 +297,10 @@ pre.wiki {
   width: 260px;
 }
 
+#qct-fieldset #field-description {
+  width: auto;
+}
+
 #qct-box #qct-cancel {
   display: inline;
   color: #08C;
@@ -478,14 +520,107 @@ input[type="submit"].btn.btn-micro {
   display: inherit !important;
 }
 
+.breadcrumb {
+  background-color: #FBFBFB;
+  background-image: -moz-linear-gradient(center top , #FFFFFF, #F5F5F5);
+  background-repeat: repeat-x;
+  border: 1px solid #DDDDDD;
+  border-radius: 3px 3px 3px 3px;
+  box-shadow: 0 1px 0 #FFFFFF inset;
+  list-style: none outside none;
+  margin: 0 0 18px;
+  padding: 7px 14px;
+}
+
 @media (max-width: 979px) {
   .hidden-desktop {
     display: inherit !important;
   }
 }
 
-.help-msg[title] {
-  cursor: help;
+@media (min-width: 979px) and (max-width: 1199px) {
+  .main-nav ul {
+    background-color: red;
+  }
+}
+
+@media (max-width: 767px) {
+  body {
+    padding-left: 8px;
+    padding-right: 8px;
+  }
+
+  #logo, #usermenu, #breadcrumbbar {
+    float: left;
+  }
+    
+  #searchbox {
+    float: left;
+    margin-right: 10px;
+  }
+
+  .breadcrumb {
+    margin-top: 7px;
+  }
+
+  .comment-meta {
+    margin-bottom: 5px;
+  }
+
+  .comment-meta .id,
+  .comment-meta .date {
+    text-align: right;
+  }
+
+  .sticky {
+    width: 100%;
+    position: relative;
+    left: 0px;
+    padding-left: 29px; 
+  }
+
+  #belowStatus {
+    margin-top: 5px;
+  }
+
+  .ticket .properties div[class*="span"] {
+    float: left;
+    margin-left: 10px;
+    margin-right: 10px;
+  }
+
+  #changelog .change .span2 {
+    float: left;
+  }
+}
+
+@media (max-width: 480px) {
+  body {
+    padding-left: 0px;
+    padding-right: 0px;
+  }
+
+  h1 {
+    font-size: 30px;
+    margin-bottom: 0px;
+    line-height: 1;
+  }
+
+  .container:first-child {
+    border-left: 0px solid white;
+    border-right: 0px solid white;
+    padding: 0px;
+  }
+
+  header, #belowStatus, #mobile-activity,
+  #stickyStatus, #footer-container {
+    padding-left: 7px;
+    padding-right: 7px;
+  }
+
+  .ownership {
+    padding-left: 15px;
+  }
 }
 
 /* Revert some changes introduced in 2.1.0 */
@@ -496,18 +631,6 @@ h6 {
   text-transform: uppercase;
 }
 
-.breadcrumb {
-  background-color: #FBFBFB;
-  background-image: -moz-linear-gradient(center top , #FFFFFF, #F5F5F5);
-  background-repeat: repeat-x;
-  border: 1px solid #DDDDDD;
-  border-radius: 3px 3px 3px 3px;
-  box-shadow: 0 1px 0 #FFFFFF inset;
-  list-style: none outside none;
-  margin: 0 0 18px;
-  padding: 7px 14px;
-}
-
 /* @end */
 
 /* @group Bootstrap extensions */
@@ -679,34 +802,116 @@ endColorstr='#ffe6e6e6', GradientType=0)
 
 /* @group Sticky Status */
 
-[class*="span"].stickyStatus {
-  margin-left: 0px;
+#stickyStatus {
+  position: relative;
+  background-color: white;
+  z-index: 20;
 }
 
-.stickyStatus.affix {
-  background-color: #FFFFFF;
-  border-bottom: 2px solid #A4A4A4;
-  position: fixed;
+/*  The box-shadow overhanging to the left and right are hacks only.
+    We should look for a better solution for production code. */
+#stickyStatus.sticky {
+  position: fixed !important;
   top: 0px;
-  z-index: 1024;
+  opacity: 1;
+  background-color: rgb(93%, 93%, 93%);
+
+  padding-bottom: 10px;
+
+  border-bottom: 1px solid rgba(75%, 75%, 75%, 0.5);
+
+  -webkit-box-shadow:  0px 6px 5px -4px rgba(50%, 50%, 50%, 0.5),
+                     -20px 0px 0px rgb(93%, 93%, 93%),
+                      20px 0px 0px rgb(93%, 93%, 93%);
+     -moz-box-shadow:  0px 6px 5px -4px rgba(50%, 50%, 50%, 0.5),
+                     -20px 0px 0px rgb(93%, 93%, 93%),
+                      20px 0px 0px rgb(93%, 93%, 93%);
+          box-shadow:  0px 6px 5px -4px rgba(50%, 50%, 50%, 0.5),
+                     -20px 0px 0px rgb(93%, 93%, 93%),
+                      20px 0px 0px rgb(93%, 93%, 93%);
+
+  -webkit-transition: all 0.3s ease-out;
+     -moz-transition: all 0.3s ease-out;
+       -o-transition: all 0.3s ease-out;
+      -ms-transition: all 0.3s ease-out;
+          transition: all 0.3s ease-out;
+}
+
+#belowStatus {
+  position: relative;
+  margin-top: 20px;
+}
+
+.offsetSticky {
+  height: 130px;
+}
+
+#stickyStatus h2,
+#stickyStatus ul {
+	margin-bottom: 3px;
+}
+
+#stickyStatus h2 {
+	line-height: 30px;
+}
+
+.local-nav {
+	display: inline-block;
+	margin-left: 0;
+}
+
+.ownership {
+	padding-left: 25px;
+}
+
+#description {
+	margin-top: 10px;
+}
+
+.comment {
+	border-top: 1px solid rgb(80%, 80%, 80%);
+	margin-bottom: 10px;
+	padding-top: 5px;
+}
+
+.comment-meta {
+		overflow: hidden;
+}
+
+.comment-meta .id,
+.comment-meta .date {
+	float: right;
+	color: #888;
+	font-size: 90%;
+}
+
+.comment-meta span:first-child {
+	font-weight: 700;
+}
+
+.add-comment textarea {
+	height: 100px;
+	border: 1px solid #eee;
+	color: lightgray;
+	display: block;
 }
 
 /* @end */
 
 /* @group Wiki Tables */
 
-TABLE.wiki {
+table.wiki {
   border: 1px solid #CCCCCC;
   border-collapse: collapse;
   border-spacing: 0;
 }
 
-TABLE.wiki td {
+table.wiki td {
   border: 1px solid #CCCCCC;
   padding: 0.1em 0.25em;
 }
 
-TABLE.wiki th {
+table.wiki th {
   border: 1px solid #BBBBBB;
   padding: 0.1em 0.25em;
   background-color: #F7F7F7;

Modified: incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html
URL: http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html?rev=1442601&r1=1442600&r2=1442601&view=diff
==============================================================================
--- incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html (original)
+++ incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_theme/bhtheme/templates/bh_search.html Tue Feb  5 14:22:55 2013
@@ -40,66 +40,60 @@
     </script>
   </head>
   <body>
-    <py:choose>
-      <div py:when="query" id="content" class="row">
-  
-        <h2 py:if="results">
-          Results <small>(${results.displayed_items()})</small>
-        </h2>
-
-        <form id="fullsearch" action="${href.search()}" method="get">
-          <input type="hidden" name="q" value="${query}" />
-          <input type="hidden" name="noquickjump" value="1" />
-          <div class="span2">
-            <input class="btn" type="submit" value="${_('Apply filters')}" />
-          </div>
-          <div class="span10 filters">
-            <py:for each="filter in filters">
-              <input type="checkbox" id="${filter.name}" name="${filter.name}"
-                     checked="${filter.active or None}" />
-              <label for="${filter.name}">
-                <span class="label">${filter.label}</span>
-              </label>
-            </py:for>
-          </div>
-        </form>
-  
-        <div class="span12" py:if="results or quickjump">
-          <div>
-            <dl id="results">
-              <py:if test="quickjump">
-                <dt id="quickjump">
-                  <a href="${quickjump.href}" i18n:msg="name">Quickjump to ${quickjump.name}</a>
-                </dt>
-                <dd>${quickjump.description}</dd>
-              </py:if>
-              <py:for each="result in results">
-                <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
-                <dd class="searchable">${result.excerpt}</dd>
-                <dd>
-                  <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
-                  <span class="date">${result.date}</span>
-                </dd>
-              </py:for>
-            </dl>
-          </div>
-          <xi:include py:with="paginator = results" href="bh_page_index.html" />
+    <div id="content" class="row">
+
+      <h2 py:if="results">
+        Results <small>(${results.displayed_items()})</small>
+      </h2>
+
+      <form id="fullsearch" action="${href.search()}" method="get">
+        <input type="hidden" name="q" value="${query}" />
+        <input type="hidden" name="noquickjump" value="1" />
+        <div class="span2">
+          <input class="btn" type="submit" value="${_('Apply filters')}" />
         </div>
-  
-        <div class="span12"
-            py:if="query and not (results or quickjump)">
-          <p id="notfound" class="alert">
-            No matches found.
-          </p>
+        <div class="span10 filters">
+          <py:for each="filter in filters">
+            <input type="checkbox" id="${filter.name}" name="${filter.name}"
+                   checked="${filter.active or None}" />
+            <label for="${filter.name}">
+              <span class="label">${filter.label}</span>
+            </label>
+          </py:for>
+        </div>
+      </form>
+
+      <div class="span12" py:if="results or quickjump">
+        <div>
+          <dl id="results">
+            <py:if test="quickjump">
+              <dt id="quickjump">
+                <a href="${quickjump.href}" i18n:msg="name">Quickjump to ${quickjump.name}</a>
+              </dt>
+              <dd>${quickjump.description}</dd>
+            </py:if>
+            <py:for each="result in results">
+              <dt><a href="${result.href}" class="searchable">${result.title}</a></dt>
+              <dd class="searchable">${result.excerpt}</dd>
+              <dd>
+                <py:if test="result.author"><span class="author" i18n:msg="author">By ${format_author(result.author)}</span> &mdash;</py:if>
+                <span class="date">${result.date}</span>
+              </dd>
+            </py:for>
+          </dl>
         </div>
-  
+        <xi:include py:with="paginator = results" href="bh_page_index.html" />
       </div>
-      <p py:otherwise="" class="alert">
-        <span class="label label-warning">Warning</span>
-        Search query too short. Query must be at least 3 characters long.
-        Please type your query string in search box and try again.
-      </p>
-    </py:choose>
+
+      <div class="span12"
+          py:if="query and not (results or quickjump)">
+        <p id="notfound" class="alert">
+          No matches found.
+        </p>
+      </div>
+
+    </div>
+
     <div id="help" class="help-block pull-right" i18n:msg="">
       <span class="label label-info">Note:</span>
       See <a href="${href.wiki('TracSearch')}">TracSearch</a>