Author: andrej Date: Tue Mar 19 14:48:22 2013 New Revision: 1458321 URL: http://svn.apache.org/r1458321 Log: Adding compatibility of bhsearch with trac search, remove tabs in bhsearch ui - towards #472 (from Anze)
Added: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html (original) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch.html Tue Mar 19 14:48:22 2013 @@ -63,18 +63,6 @@ <dd>${quickjump.description}</dd> </py:if> - <!--This just a prototype implementation. Should be replaced by proper UI mocks--> - <div> - <!--Render type tabs: All, Ticket, Wiki, etc.--> - <ul class="nav nav-tabs" id="search_types"> - <li py:for="idx, item in enumerate(i for i in types)" - class="${classes(first_last(idx, types), active=item.active)}"> - <a href="${item.href}">${item.label}</a> - </li> - - </ul> - </div> - <div py:if="results" class="row"> <div class="span3 facets"> <py:if test="facet_counts"> Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html (original) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/bhsearch_breadcrumbs.html Tue Mar 19 14:48:22 2013 @@ -23,11 +23,11 @@ xmlns:py="http://genshi.edgewall.org/" xmlns:i18n="http://genshi.edgewall.org/i18n" xmlns:xi="http://www.w3.org/2001/XInclude"> -<a href="${href.bhsearch()}">Search</a> +<a py:if="active_filter_queries or query" href="${href.bhsearch()}">Search</a> <py:for each="active_filter in active_filter_queries"> > <a href="${active_filter.href}">${active_filter.label}</a> </py:for> <py:if test="query"> - > "${query}" + > <a href="${active_query.href}">${active_query.label}</a> </py:if> </html> Added: incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml?rev=1458321&view=auto ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml (added) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/templates/opensearch.xml Tue Mar 19 14:48:22 2013 @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<OpenSearchDescription xmlns:py="http://genshi.edgewall.org/" + xmlns="http://a9.com/-/spec/opensearch/1.1/"> + <ShortName>Search $project.name</ShortName> + <InputEncoding>UTF-8</InputEncoding> + <Image py:if="chrome.icon.abs_src" width="16" height="16" + type="$chrome.icon.mimetype">$chrome.icon.abs_src</Image> + <Url type="text/html" + template="${abs_href.bhsearch()}?q={searchTerms}&page={startPage}"/> +</OpenSearchDescription> Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py (original) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/base.py Tue Mar 19 14:48:22 2013 @@ -45,9 +45,10 @@ class BaseBloodhoundSearchTest(unittest. if create_req: self.req = Mock( perm=MockPerm(), - chrome={'logo': {}}, + chrome={'logo': {}, 'links': {}}, href=Href("/main"), base_path=BASE_PATH, + path_info='/bhsearch', args=arg_list_to_args([]), ) Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py (original) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/tests/web_ui.py Tue Mar 19 14:48:22 2013 @@ -19,15 +19,16 @@ # under the License. import unittest -from urllib import urlencode, unquote +from urllib import urlencode, unquote, unquote_plus from bhsearch.api import ASC, DESC, SortInstruction from bhsearch.tests.base import BaseBloodhoundSearchTest -from bhsearch.web_ui import RequestParameters +from bhsearch.web_ui import RequestParameters, BloodhoundSearchModule from bhsearch.whoosh_backend import WhooshBackend from trac.test import Mock, MockPerm from trac.core import TracError +from trac.search.web_ui import SearchModule as TracSearchModule from trac.util.datefmt import FixedOffset from trac.util import format_datetime from trac.web import Href, arg_list_to_args, parse_arg_list, RequestDone @@ -46,15 +47,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh whoosh_backend = WhooshBackend(self.env) whoosh_backend.recreate_index() - self.req = Mock( - perm=MockPerm(), - chrome={'logo': {}}, - href=Href("/main"), - base_path=BASE_PATH, - args=arg_list_to_args([]), - redirect=self.redirect - ) - + self.req.redirect = self.redirect self.redirect_url = None self.redirect_permanent = None @@ -141,19 +134,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh data = self.process_request() #assert extra_search_options = dict(data["extra_search_fields"]) - self.assertEquals("ticket", extra_search_options['type']) - - resource_types = data["types"] - - all = resource_types[0] - self._assertResourceType(all, "All", False) - self.assertNotIn("type", all["href"]) - - ticket = resource_types[1] - self._assertResourceType(ticket, "Ticket", True, "type=ticket") - - wiki = resource_types[2] - self._assertResourceType(wiki, "Wiki", False, "type=wiki") + self.assertEqual("ticket", extra_search_options['type']) def test_type_parameter_in_links(self): self._insert_tickets(12) @@ -179,51 +160,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh for page in results.shown_pages: self.assertIn('type=ticket', page["href"]) - def test_type_grouping(self): - self.req.args[RequestParameters.QUERY] = "*:*" - data = self.process_request() - resource_types = data["types"] - - all = resource_types[0] - self._assertResourceType(all, "All", True) - self.assertNotIn("type", all["href"]) - - ticket = resource_types[1] - self._assertResourceType(ticket, "Ticket", False, "type=ticket") - - wiki = resource_types[2] - self._assertResourceType(wiki, "Wiki", False, "type=wiki") - - - def test_that_there_are_no_page_parameters_for_other_types(self): - #arrange - self._insert_tickets(12) - #act - self.req.args[RequestParameters.QUERY] = "*" - self.req.args[RequestParameters.PAGELEN] = "4" - self.req.args[RequestParameters.PAGE] = "2" - data = self.process_request() - #assert - resource_types = data["types"] - - all = resource_types[0] - self.assertNotIn("page=2", all["href"]) - - ticket = resource_types[1] - self.assertNotIn("page=", ticket["href"]) - - def test_that_there_are_filters_in_type_links(self): - #arrange -# self._insert_tickets(2) - #act - self.req.args[RequestParameters.QUERY] = "*" - self.req.args[RequestParameters.TYPE] = "ticket" - self.req.args[RequestParameters.FILTER_QUERY] = "status:new" - data = self.process_request() - #assert - for type in data["types"]: - self.assertNotIn("fq=", type["href"]) - def test_that_type_facet_is_in_default_search(self): #arrange self._insert_tickets(2) @@ -242,7 +178,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh self.req.args[RequestParameters.QUERY] = "*" data = self.process_request() #assert - facet_counts = data["facet_counts"] + facet_counts = data["facet_counts"] status_counts = facet_counts["status"] self.assertEquals(1, status_counts["new"]["count"]) self.assertEquals(1, status_counts["closed"]["count"]) @@ -256,7 +192,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh self.req.args[RequestParameters.QUERY] = "*" data = self.process_request() #assert - facet_counts = data["facet_counts"] + facet_counts = data["facet_counts"] status_counts = facet_counts["status"] self.assertEquals(1, status_counts["new"]["count"]) self.assertIn("fq=status%3A%22new%22", status_counts["new"]["href"]) @@ -270,7 +206,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh self.req.args[RequestParameters.QUERY] = "*" data = self.process_request() #assert - facet_counts = data["facet_counts"] + facet_counts = data["facet_counts"] status_counts = facet_counts["status"] empty_status_count = status_counts[None] self.assertEquals(2, empty_status_count["count"]) @@ -280,13 +216,13 @@ class WebUiTestCaseWithWhoosh(BaseBloodh def test_can_return_empty_facets_result_for_wiki_pages(self): #arrange - self.insert_wiki("W1","Some text") + self.insert_wiki("W1", "Some text") #act self.req.args[RequestParameters.TYPE] = "wiki" self.req.args[RequestParameters.QUERY] = "*" data = self.process_request() #assert - facet_counts = data["facet_counts"] + facet_counts = data["facet_counts"] self.assertEquals({}, facet_counts) def test_can_accept_multiple_filter_query_parameters(self): @@ -357,14 +293,23 @@ class WebUiTestCaseWithWhoosh(BaseBloodh data = self.process_request() #assert current_filter_queries = data["active_filter_queries"] - self.assertEquals(2, len(current_filter_queries)) + self.assertEquals(3, len(current_filter_queries)) - component_filter = current_filter_queries[0] + type_filter = current_filter_queries[0] + self.assertEquals('Ticket', type_filter["label"]) + self.assertNotIn("type=", type_filter["href"]) + self.assertNotIn('fq=', unquote(type_filter["href"])) + + component_filter = current_filter_queries[1] self.assertEquals('component:"c1"', component_filter["label"]) - self.assertNotIn("fq=", component_filter["href"]) + self.assertIn('type=ticket', component_filter["href"]) + self.assertNotIn('fq=component:"c1"', + unquote(component_filter["href"])) + self.assertIn('fq=status:"new"', unquote(component_filter["href"])) - status_filter = current_filter_queries[1] + status_filter = current_filter_queries[2] self.assertEquals('status:"new"', status_filter["label"]) + self.assertIn('type=ticket', status_filter["href"]) self.assertIn('fq=component:"c1"', unquote(status_filter["href"])) self.assertNotIn('fq=status:"new"', unquote(status_filter["href"])) @@ -587,8 +532,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh data = self.process_request() #assert extra_search_options = dict(data["extra_search_fields"]) - self.assertEquals("id, time desc", extra_search_options["sort"]) - #self.assertNotIn("sort=", active_sort["href"]) + self.assertEqual("id, time desc", extra_search_options["sort"]) def test_that_document_summary_contains_highlighted_search_terms(self): term = "searchterm" @@ -655,7 +599,6 @@ class WebUiTestCaseWithWhoosh(BaseBloodh print id, title self.assertIn(id, str(title)) - def test_that_id_is_highlighted_in_title(self): self.insert_ticket("some summary") id = "1" @@ -676,13 +619,159 @@ class WebUiTestCaseWithWhoosh(BaseBloodh self.assertLess(len(row['content']), 500) self.assertLess(len(row['hilited_content']), 500) + def test_compatibility_with_legacy_search(self): + self.env.config.set('bhsearch', 'enable_redirect', "True") + self.req.path_info = '/search' + + self.assertRaises(RequestDone, self.process_request) + self.assertIn('/bhsearch', self.redirect_url) + self.assertEqual(self.redirect_permanent, True) + + self.req.args['wiki'] = 'on' + self.assertRaises(RequestDone, self.process_request) + redirect_url = unquote_plus(self.redirect_url) + self.assertIn('/bhsearch', redirect_url) + self.assertIn('type=wiki', redirect_url) + self.assertEqual(self.redirect_permanent, True) + + self.req.args['ticket'] = 'on' + self.assertRaises(RequestDone, self.process_request) + redirect_url = unquote_plus(self.redirect_url) + self.assertIn('fq=type:(ticket OR wiki)', redirect_url) + self.assertIn('/bhsearch', self.redirect_url) + self.assertEqual(self.redirect_permanent, True) + + self.req.args['milestone'] = 'on' + self.assertRaises(RequestDone, self.process_request) + redirect_url = unquote_plus(self.redirect_url) + self.assertIn('fq=type:(ticket OR wiki OR milestone)', redirect_url) + self.assertIn('/bhsearch', self.redirect_url) + self.assertEqual(self.redirect_permanent, True) + + self.req.args['changeset'] = 'on' + self.assertRaises(RequestDone, self.process_request) + redirect_url = unquote_plus(self.redirect_url) + self.assertIn('fq=type:(ticket OR wiki OR milestone OR changeset)', redirect_url) + self.assertIn('/bhsearch', self.redirect_url) + self.assertEqual(self.redirect_permanent, True) + + def test_opensearch_integration(self): + # pylint: disable=unused-variable + self.req.path_info = '/bhsearch/opensearch' + bhsearch = BloodhoundSearchModule(self.env) + + url, data, x = bhsearch.process_request(self.req) + + self.assertEqual(url, 'opensearch.xml') + + def test_returns_correct_handler(self): + bhsearch = BloodhoundSearchModule(self.env) + tracsearch = self.env[TracSearchModule] + + class PathInfoSetter(object): + # pylint: disable=incomplete-protocol + def __setitem__(other, key, value): + if key == "PATH_INFO": + self.req.path_info = value + self.req.environ = PathInfoSetter() + + self.env.config.set('bhsearch', 'enable_redirect', "True") + + self.req.path_info = '/search' + self.assertIs(bhsearch.pre_process_request(self.req, tracsearch), + bhsearch) + + self.req.path_info = '/bhsearch' + self.assertIs(bhsearch.pre_process_request(self.req, tracsearch), + bhsearch) + + self.env.config.set('bhsearch', 'enable_redirect', "False") + # With redirect disabled, handler should not be changed. + self.req.path_info = '/search' + self.assertIs(bhsearch.pre_process_request(self.req, None), + None) + + self.req.path_info = '/bhsearch' + self.assertIs(bhsearch.pre_process_request(self.req, None), + None) + + def test_that_correct_search_handle_is_selected_for_quick_search(self): + bhsearch = BloodhoundSearchModule(self.env) + + def process_request(path, enable_redirect, is_default): + # pylint: disable=unused-variable + self.req.path_info = path + self.env.config.set('bhsearch', 'enable_redirect', + str(enable_redirect)) + self.env.config.set('bhsearch', 'is_default', str(is_default)) + template, data, content_type = \ + bhsearch.post_process_request(self.req, '', {}, '') + return data + + data = process_request('/', enable_redirect=False, is_default=False) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.search()) + + data = process_request('/', enable_redirect=True, is_default=False) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.bhsearch()) + + data = process_request('/', enable_redirect=False, is_default=True) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.bhsearch()) + + data = process_request('/', enable_redirect=True, is_default=True) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.bhsearch()) + + for is_default in [False, True]: + data = process_request('/search', + enable_redirect=False, + is_default=is_default) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.search()) + + for is_default in [False, True]: + data = process_request('/search', + enable_redirect=True, + is_default=is_default) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], self.req.href.bhsearch()) + + for enable_redirect in [False, True]: + for is_default in [False, True]: + data = process_request('/bhsearch', + enable_redirect=enable_redirect, + is_default=is_default) + self.assertIn('search_handler', data) + self.assertEqual(data['search_handler'], + self.req.href.bhsearch()) + + def test_that_active_query_is_set(self): + #arrange + self.insert_ticket("Ticket 1", component="c1", status="new") + self.insert_ticket("Ticket 2", component="c1", status="new") + #act + self.req.args[RequestParameters.TYPE] = "ticket" + self.req.args[RequestParameters.QUERY] = "Ticket" + self.req.args[RequestParameters.FILTER_QUERY] = [ + 'component:"c1"', + 'status:"new"'] + data = self.process_request() + #assert + active_query = data["active_query"] + self.assertEqual(active_query["label"], '"Ticket"') + self.assertEqual(active_query["query"], "Ticket") + self.assertNotIn("?q=", unquote(active_query["href"])) + self.assertNotIn("&q=", unquote(active_query["href"])) + self.assertIn("fq=", unquote(active_query["href"])) + def _find_header(self, headers, name): for header in headers: if header["name"] == name: return header raise Exception("Header not found: %s" % name) - def _count_parameter_in_url(self, url, parameter_name, value): parameter_to_find = (parameter_name, value) parsed_parameters = parse_arg_list(url) @@ -707,6 +796,7 @@ class WebUiTestCaseWithWhoosh(BaseBloodh for i in range(1, n+1): self.insert_wiki("test %s" % i) + class RequestParametersTest(unittest.TestCase): def setUp(self): self.req = Mock( Modified: incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py (original) +++ incubator/bloodhound/trunk/bloodhound_search/bhsearch/web_ui.py Tue Mar 19 14:48:22 2013 @@ -31,20 +31,24 @@ from genshi.builder import tag from genshi import HTML from trac.perm import IPermissionRequestor from trac.search import shorten_result -from trac.config import OrderedExtensionsOption, ListOption, Option +from trac.config import OrderedExtensionsOption, ListOption, Option, BoolOption from trac.util.presentation import Paginator from trac.util.datefmt import format_datetime, user_time -from trac.web import IRequestHandler +from trac.web import IRequestHandler, IRequestFilter 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, prevnext_nav, + web_context) from bhsearch.api import (BloodhoundSearchApi, ISearchParticipant, SCORE, ASC, DESC, IndexFields, SortInstruction) from trac.wiki.formatter import extract_link SEARCH_PERMISSION = 'SEARCH_VIEW' DEFAULT_RESULTS_PER_PAGE = 10 +SEARCH_URL = '/search' +BHSEARCH_URL = '/bhsearch' +SEARCH_URLS_RE = re.compile(r'/(?P<prefix>bh)?search(?P<suffix>.*)') class RequestParameters(object): """ @@ -132,8 +136,6 @@ class RequestParameters(object): return sort if sort else None - - def _remove_possible_duplications(self, parameters_list): seen = set() return [parameter for parameter in parameters_list @@ -151,6 +153,7 @@ class RequestParameters(object): skip_view=False, sort=None, skip_sort = False, + skip_query = False ): params = copy.deepcopy(self.params) @@ -183,6 +186,9 @@ class RequestParameters(object): elif force_filters is not None: params[self.FILTER_QUERY] = force_filters + if skip_query: + self._delete_if_exists(params, self.QUERY) + return self.req.href.bhsearch(**params) def _create_sort_expression(self, sort): @@ -204,10 +210,11 @@ class RequestParameters(object): if name in params: del params[name] + class BloodhoundSearchModule(Component): """Main search page""" implements(INavigationContributor, IPermissionRequestor, IRequestHandler, - ITemplateProvider, + ITemplateProvider, IRequestFilter # IWikiSyntaxProvider #todo: implement later ) @@ -245,6 +252,10 @@ class BloodhoundSearchModule(Component): default=",".join(default_grid_fields), doc="""Default fields for grid view for specific resource""") + default_search = BoolOption('bhsearch', 'is_default', False) + + redirect_enabled = BoolOption('bhsearch', 'enable_redirect', + False) # INavigationContributor methods def get_active_navigation_item(self, req): @@ -263,10 +274,15 @@ class BloodhoundSearchModule(Component): # IRequestHandler methods def match_request(self, req): - return re.match(r'/bhsearch?', req.path_info) is not None + return re.match('^%s' % BHSEARCH_URL, req.path_info) is not None def process_request(self, req): req.perm.assert_permission(SEARCH_PERMISSION) + + if self._is_opensearch_request(req): + return ('opensearch.xml', {}, + 'application/opensearchdescription+xml') + request_context = RequestContext( self.env, req, @@ -276,6 +292,9 @@ class BloodhoundSearchModule(Component): self.default_facets ) + if request_context.requires_redirect: + req.redirect(request_context.parameters.create_href(), True) + # compatibility with legacy search req.search_query = request_context.parameters.query @@ -294,6 +313,9 @@ class BloodhoundSearchModule(Component): request_context.process_results(query_result) return self._return_data(req, request_context.data) + def _is_opensearch_request(self, req): + return req.path_info == BHSEARCH_URL + '/opensearch' + def _return_data(self, req, data): add_stylesheet(req, 'common/css/search.css') return 'bhsearch.html', data, None @@ -307,10 +329,29 @@ class BloodhoundSearchModule(Component): def get_templates_dirs(self): return [pkg_resources.resource_filename(__name__, 'templates')] + # IRequestFilter methods + def pre_process_request(self, req, handler): + if SEARCH_URLS_RE.match(req.path_info): + if self.redirect_enabled: + return self + return handler + + def post_process_request(self, req, template, data, content_type): + if self.redirect_enabled: + data['search_handler'] = req.href.bhsearch() + elif req.path_info.startswith(SEARCH_URL): + data['search_handler'] = req.href.search() + elif self.default_search or req.path_info.startswith(BHSEARCH_URL): + data['search_handler'] = req.href.bhsearch() + else: + data['search_handler'] = req.href.search() + return template, data, content_type + + class RequestContext(object): DATA_ACTIVE_FILTER_QUERIES = 'active_filter_queries' + DATA_ACTIVE_QUERY = 'active_query' DATA_BREADCRUMBS_TEMPLATE = 'resourcepath_template' - DATA_TYPES = "types" DATA_HEADERS = "headers" DATA_ALL_VIEWS = "all_views" DATA_VIEW = "view" @@ -359,13 +400,9 @@ class RequestContext(object): if self.parameters.sort: self.sort = self.parameters.sort - self.data[self.DATA_SEARCH_EXTRAS].append( - (RequestParameters.SORT, self.parameters.sort_string) - ) else: self.sort = self.DEFAULT_SORT - self.allowed_participants, self.sorted_participants = \ self._get_allowed_participants(req) @@ -377,16 +414,20 @@ class RequestContext(object): self.active_type = None self.active_participant = None - self._prepare_allowed_types() - self._prepare_active_filter_queries() + self._prepare_active_type() + self._prepare_breadcrumb_items() + self._prepare_hidden_search_fields() self._prepare_quick_jump() + # Compatibility with trac search + self._process_legacy_type_filters(req, search_participants) + if req.path_info.startswith(SEARCH_URL): + self.requires_redirect = True + self.fields = self._prepare_fields_and_view() self.query_filter = self._prepare_query_filter() self.facets = self._prepare_facets() - - def _get_allowed_participants(self, req): allowed_participants = {} ordered_participants = [] @@ -397,58 +438,67 @@ class RequestContext(object): ordered_participants.append(participant) return allowed_participants, ordered_participants - - def _prepare_allowed_types(self): + def _prepare_active_type(self): active_type = self.parameters.type if active_type and active_type not in self.allowed_participants: raise TracError(_("Unsupported resource type: '%(name)s'", - name=active_type)) - allowed_types = [ - dict( - label=_("All"), - active=(active_type is None), - href=self.parameters.create_href( - skip_type=True, - skip_page=True, - force_filters=[], - ), - ) - ] - #we want obtain the same order as in search participants options - for participant in self.sorted_participants: - allowed_types.append(dict( - label=_(participant.get_title()), - active=(participant.get_participant_type() == active_type), - href=self.parameters.create_href( - type=participant.get_participant_type(), - skip_page=True, - force_filters=[], - ), - )) - self.data[self.DATA_TYPES] = allowed_types - self.data[self.DATA_SEARCH_EXTRAS].append( - (RequestParameters.TYPE, active_type) - ) + name=active_type)) - def _prepare_active_filter_queries(self): + def _prepare_breadcrumb_items(self): current_filters = self.parameters.filter_queries - def remove_filters_from_list(filer_to_cut_from): - return current_filters[:current_filters.index(filer_to_cut_from)] + def remove_filter_from_list(filter_to_remove): + new_filters = list(current_filters) + new_filters.remove(filter_to_remove) + return new_filters + + if self.active_type: + type_query = self._create_term_expression('type', self.active_type) + type_filters = [dict( + href=self.parameters.create_href(skip_type=True, + force_filters=[]), + label=unicode(self.active_type).capitalize(), + query=type_query, + )] + else: + type_filters = [] active_filter_queries = [ dict( href=self.parameters.create_href( - force_filters=remove_filters_from_list(filter_query) + force_filters=remove_filter_from_list(filter_query) ), label=filter_query, query=filter_query, ) for filter_query in self.parameters.filter_queries ] - self.data[self.DATA_ACTIVE_FILTER_QUERIES] = active_filter_queries - for filter_query in active_filter_queries: + active_query = dict( + href=self.parameters.create_href(skip_query=True), + label=u'"%s"' % self.parameters.query, + query=self.parameters.query + ) + + self.data[self.DATA_ACTIVE_FILTER_QUERIES] = \ + type_filters + active_filter_queries + self.data[self.DATA_ACTIVE_QUERY] = active_query + + def _prepare_hidden_search_fields(self): + if self.active_type: + self.data[self.DATA_SEARCH_EXTRAS].append( + (RequestParameters.TYPE, self.active_type) + ) + + if self.parameters.view: self.data[self.DATA_SEARCH_EXTRAS].append( - (RequestParameters.FILTER_QUERY, filter_query['query']) + (RequestParameters.VIEW, self.parameters.view) + ) + if self.parameters.sort: + self.data[self.DATA_SEARCH_EXTRAS].append( + (RequestParameters.SORT, self.parameters.sort_string) + ) + for filter_query in self.parameters.filter_queries: + self.data[self.DATA_SEARCH_EXTRAS].append( + (RequestParameters.FILTER_QUERY, filter_query) ) def _prepare_quick_jump(self): @@ -488,7 +538,6 @@ class RequestContext(object): else: req.redirect(quickjump_href) - def _prepare_fields_and_view(self): self._add_views_selector() self.view = self._get_view() @@ -511,10 +560,6 @@ class RequestContext(object): def _add_views_selector(self): active_view = self.parameters.view - if active_view: - self.data[self.DATA_SEARCH_EXTRAS].append( - (RequestParameters.VIEW, active_view) - ) all_views = [] for view, label in self.VIEWS_SUPPORTED.iteritems(): @@ -589,11 +634,31 @@ class RequestContext(object): def _prepare_facets(self): #TODO: add possibility of specifying facets in query parameters if self.active_participant: - facets = self.active_participant.get_default_facets() + facets = self.active_participant.get_default_facets() else: - facets = self.default_facets + facets = self.default_facets return facets + def _process_legacy_type_filters(self, req, search_participants): + legacy_type_filters = [sp.get_participant_type() + for sp in search_participants + if sp.get_participant_type() in req.args] + if legacy_type_filters: + params = self.parameters.params + if len(legacy_type_filters) == 1: + self.parameters.type = params[RequestParameters.TYPE] = \ + legacy_type_filters[0] + else: + filter_queries = self.parameters.filter_queries + if params[RequestParameters.FILTER_QUERY] is not filter_queries: + params[RequestParameters.FILTER_QUERY] = filter_queries + filter_queries.append( + 'type:(%s)' % ' OR '.join(legacy_type_filters) + ) + self.requires_redirect = True + else: + self.requires_redirect = False + def _process_doc(self, doc): ui_doc = dict(doc) ui_doc["href"] = self.req.href(doc['type'], doc['id']) @@ -636,6 +701,7 @@ class RequestContext(object): add_link(self.req, 'prev', prev_href, _('Previous Page')) self.data[self.DATA_RESULTS] = results + prevnext_nav(self.req, _('Previous'), _('Next')) def _prepare_shown_pages(self, results): shown_pages = results.get_shown_pages(self.pagelen) Modified: incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css (original) +++ incubator/bloodhound/trunk/bloodhound_theme/bhtheme/htdocs/bloodhound.css Tue Mar 19 14:48:22 2013 @@ -626,9 +626,9 @@ input[type="submit"].btn.btn-micro { } @media (min-width: 1200px) { - .search_results { - width: 920px; - } + .span9.search_results { + width: 920px; + } } @media (min-width: 979px) and (max-width: 1199px) { Modified: incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html (original) +++ incubator/bloodhound/trunk/bloodhound_theme/bhtheme/templates/bloodhound_theme.html Tue Mar 19 14:48:22 2013 @@ -91,10 +91,7 @@ <div id="searchbox" class="btn-toolbar"> <div class="btn-group"> <form id="mainsearch" class="form-inline" - action="${href.search() if req.path_info == u'/search' else - href.bhsearch() if (req.path_info == u'/bhsearch' or - is_bhsearch_default) else - href.search()}" + action="${search_handler or href.search()}" method="get"> <div class="input-append"> <input type="text" class="span3" id="q" name="q" Modified: incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py?rev=1458321&r1=1458320&r2=1458321&view=diff ============================================================================== --- incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py (original) +++ incubator/bloodhound/trunk/bloodhound_theme/bhtheme/theme.py Tue Mar 19 14:48:22 2013 @@ -155,8 +155,6 @@ class BloodhoundTheme(ThemeBase): labels_footer_right = Option('labels', 'footer_right', '', """Text to use as the right aligned footer""") - is_bhsearch_default = BoolOption('bhsearch', 'is_default', False) - _wiki_pages = None Chrome.default_html_doctype = DocType.HTML5 @@ -283,7 +281,6 @@ class BloodhoundTheme(ThemeBase): if is_active_theme and data is not None: data['responsive_layout'] = self.env.config.getbool( 'bloodhound', 'responsive_layout', 'true') - data['is_bhsearch_default'] = self.is_bhsearch_default return template, data, content_type