search: new UI for search, and repo group context search

- expose search into main NAV
- have repo group search filter using new ES lang
- show token keys and curretn context when searching
- ui updated and consistency fixes
This commit is contained in:
Daniel Dourvaris 2019-01-30 21:20:26 +01:00
parent 427c87316b
commit 4eccdc24d9
17 changed files with 223 additions and 133 deletions

View file

@ -334,6 +334,13 @@ class RepoGroupAppView(BaseAppView):
self.db_repo_group = request.db_repo_group
self.db_repo_group_name = self.db_repo_group.group_name
def _get_local_tmpl_context(self, include_app_defaults=True):
_ = self.request.translate
c = super(RepoGroupAppView, self)._get_local_tmpl_context(
include_app_defaults=include_app_defaults)
c.repo_group = self.db_repo_group
return c
def _revoke_perms_on_yourself(self, form_result):
_updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
form_result['perm_updates'])

View file

@ -334,6 +334,7 @@ class HomeView(BaseAppView):
def _get_default_search_queries(self, search_context, searcher, query):
if not searcher:
return []
is_es_6 = searcher.is_es_6
queries = []
@ -424,12 +425,20 @@ class HomeView(BaseAppView):
{
'id': -1,
'value': query,
'value_display': u'Search for: `{}`'.format(query),
'value_display': u'Commit search for: `{}`'.format(query),
'type': 'search',
'url': h.route_path('search',
_query={'q': query, 'type': 'content'})
}
)
})
queries.append(
{
'id': -1,
'value': query,
'value_display': u'File search for: `{}`'.format(query),
'type': 'search',
'url': h.route_path('search',
_query={'q': query, 'type': 'commit'})
})
return queries

View file

@ -41,7 +41,7 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
execution_time = ''
schema = search_schema.SearchParamsSchema()
search_tags = []
search_params = {}
errors = []
try:
@ -85,6 +85,8 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
finally:
searcher.cleanup()
search_tags = searcher.extract_search_tags(search_query)
if not search_result['error']:
execution_time = '%s results (%.3f seconds)' % (
search_result['count'],
@ -105,6 +107,7 @@ def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
c.cur_query = search_query
c.search_type = search_type
c.searcher = searcher
c.search_tags = search_tags
class SearchView(BaseAppView):

View file

@ -1495,6 +1495,28 @@ def breadcrumb_repo_link(repo):
return literal(' » '.join(path))
def breadcrumb_repo_group_link(repo_group):
"""
Makes a breadcrumbs path link to repo
ex::
group >> subgroup
:param repo_group: a Repository Group instance
"""
path = [
link_to(group.name,
route_path('repo_group_home', repo_group_name=group.group_name))
for group in repo_group.parents
] + [
link_to(repo_group.name,
route_path('repo_group_home', repo_group_name=repo_group.group_name))
]
return literal(' » '.join(path))
def format_byte_size_binary(file_size):
"""
Formats file/folder sizes to standard.

View file

@ -76,6 +76,10 @@ class BaseSearcher(object):
def get_handlers(self):
return {}
@staticmethod
def extract_search_tags(query):
return []
def search_config(config, prefix='search.'):
_config = {}

View file

@ -195,63 +195,3 @@ def get_matching_line_offsets(lines, terms=None, markers=None):
matching_lines[line_index] = match_offsets
return line_index, matching_lines
def lucene_query_parser():
# from pyparsing lucene_grammar
from pyparsing import (
Literal, CaselessKeyword, Forward, Regex, QuotedString, Suppress,
Optional, Group, infixNotation, opAssoc, ParserElement, pyparsing_common)
ParserElement.enablePackrat()
COLON, LBRACK, RBRACK, LBRACE, RBRACE, TILDE, CARAT = map(Literal, ":[]{}~^")
LPAR, RPAR = map(Suppress, "()")
and_, or_, not_, to_ = map(CaselessKeyword, "AND OR NOT TO".split())
keyword = and_ | or_ | not_ | to_
expression = Forward()
valid_word = Regex(r'([a-zA-Z0-9*_+.-]|\\[!(){}\[\]^"~*?\\:])+').setName("word")
valid_word.setParseAction(
lambda t: t[0]
.replace('\\\\', chr(127))
.replace('\\', '')
.replace(chr(127), '\\')
)
string = QuotedString('"')
required_modifier = Literal("+")("required")
prohibit_modifier = Literal("-")("prohibit")
integer = Regex(r"\d+").setParseAction(lambda t: int(t[0]))
proximity_modifier = Group(TILDE + integer("proximity"))
number = pyparsing_common.fnumber()
fuzzy_modifier = TILDE + Optional(number, default=0.5)("fuzzy")
term = Forward()
field_name = valid_word().setName("fieldname")
incl_range_search = Group(LBRACK + term("lower") + to_ + term("upper") + RBRACK)
excl_range_search = Group(LBRACE + term("lower") + to_ + term("upper") + RBRACE)
range_search = incl_range_search("incl_range") | excl_range_search("excl_range")
boost = (CARAT + number("boost"))
string_expr = Group(string + proximity_modifier) | string
word_expr = Group(valid_word + fuzzy_modifier) | valid_word
term << (Optional(field_name("field") + COLON) +
(word_expr | string_expr | range_search | Group(
LPAR + expression + RPAR)) +
Optional(boost))
term.setParseAction(lambda t: [t] if 'field' in t or 'boost' in t else None)
expression << infixNotation(
term,
[
(required_modifier | prohibit_modifier, 1, opAssoc.RIGHT),
((not_ | '!').setParseAction(lambda: "NOT"), 1, opAssoc.RIGHT),
((and_ | '&&').setParseAction(lambda: "AND"), 2, opAssoc.LEFT),
(Optional(or_ | '||').setParseAction(lambda: "OR"), 2, opAssoc.LEFT),
]
)
return expression

View file

@ -2236,6 +2236,10 @@ h3.files_location{
clear: both;
margin: 0 0 @padding;
}
.search-tags {
padding: 5px 0;
}
}
div.search-feedback-items {

View file

@ -300,6 +300,10 @@ mark,
margin-top: @padding;
}
}
.repo-group-desc {
padding: 8px 0px 0px 0px;
}
}
.title-main {

View file

@ -281,7 +281,9 @@ function registerRCRoutes() {
pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
pyroutes.register('search', '/_admin/search', []);
pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);

View file

@ -4,17 +4,9 @@
<%def name="breadcrumbs_links()">
%if c.repo:
${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
&raquo;
${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
${_('Settings')}
%elif c.repo_group:
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
&raquo;
${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
&raquo;
${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
${_('Settings')}
%else:
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;

View file

@ -8,37 +8,23 @@
%endif
</%def>
<%def name="breadcrumbs_links()">
${h.link_to(_('Admin'),h.route_path('admin_home'))}
&raquo;
${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
%if c.repo_group.parent_group:
&raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
%endif
&raquo; ${c.repo_group.name}
</%def>
<%def name="breadcrumbs_side_links()">
<ul class="links">
<li>
<a href="${h.route_path('repo_group_new', _query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
</li>
</ul>
</%def>
<%def name="menu_bar_nav()">
${self.menu_items(active='admin')}
</%def>
<%def name="menu_bar_subnav()">
${self.repo_group_menu(active='options')}
</%def>
<%def name="main_content()">
<%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
</%def>
<%def name="main()">
<div class="box">
<div class="title">
${self.breadcrumbs()}
${self.breadcrumbs_side_links()}
${self.repo_group_page_title(c.repo_group)}
</div>
<div class="sidebar-col-wrapper">
@ -58,4 +44,5 @@
</div>
</div>
</%def>

View file

@ -36,7 +36,7 @@
<div class="box">
<div class="title">
${self.repo_page_title(c.rhodecode_db_repo)}
${self.breadcrumbs()}
</div>
<div class="sidebar-col-wrapper scw-small">

View file

@ -180,7 +180,7 @@
%endif
## repo name with group name
${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
${h.breadcrumb_repo_link(repo_instance)}
</div>
@ -251,7 +251,7 @@
</a>
<ul class="submenu">
%if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
<li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
<li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
%endif
%if c.rhodecode_db_repo.fork:
<li>
@ -297,6 +297,73 @@
</%def>
<%def name="repo_group_page_title(repo_group_instance)">
<div class="title-content">
<div class="title-main">
## Repository Group icon
<i class="icon-folder-close"></i>
## repo name with group name
${h.breadcrumb_repo_group_link(repo_group_instance)}
</div>
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
<div class="repo-group-desc">
${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
</div>
</div>
</%def>
<%def name="repo_group_menu(active=None)">
<%
def is_active(selected):
if selected == active:
return "active"
is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
gr_name = c.repo_group.group_name if c.repo_group else None
# create repositories with write permission on group is set to true
create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
%>
<!--- CONTEXT BAR -->
<div id="context-bar">
<div class="wrapper">
<ul id="context-pages" class="navigation horizontal-list">
<li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
<li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo_group', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Search')}</div></a></li>
<li class="${is_active('options')}">
<a class="menulink dropdown">
<div class="menulabel">${_('Options')} <div class="show_more"></div></div>
</a>
<ul class="submenu">
%if is_admin or group_admin:
<li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
%endif
%if is_admin or group_admin or (group_write and create_on_write):
<li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
%endif
%if is_admin or group_admin:
<li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
%endif
</ul>
</li>
</ul>
</div>
<div class="clear"></div>
</div>
<!--- END CONTEXT BAR -->
</%def>
<%def name="usermenu(active=False)">
## USER MENU
<li id="quick_login_li" class="${'active' if active else ''}">

View file

@ -1,13 +1,27 @@
<%inherit file="/base/base.mako"/>
<%def name="menu_bar_subnav()">
% if c.repo_group:
${self.repo_group_menu(active='home')}
% endif
</%def>
<%def name="main()">
<div class="box">
<!-- box / title -->
<div class="title">
<div class="block-left breadcrumbs">
${self.breadcrumbs()}
<span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span>
% if c.repo_group:
${self.repo_group_page_title(c.repo_group)}
## context actions
<div>
<ul class="links icon-only-links block-right">
<li></li>
</ul>
</div>
% endif
%if c.rhodecode_user.username != h.DEFAULT_USER:
<div class="block-right">
<%
@ -15,12 +29,6 @@
create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
gr_name = c.repo_group.group_name if c.repo_group else None
# create repositories with write permission on group is set to true
create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
%>
%if not c.repo_group:
@ -32,17 +40,6 @@
%if is_admin or create_repo_group:
<a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
%endif
%else:
##we're inside other repository group other terms apply
%if is_admin or group_admin or (group_write and create_on_write):
<a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
%endif
%if is_admin or group_admin:
<a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
%endif
%if is_admin or group_admin:
<a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
%endif
%endif
</div>
%endif

View file

@ -33,7 +33,7 @@
<div class="pr-details-title">
${_('Pull request summary')}
${_('Summary')}
</div>
<div class="form" style="padding-top: 10px">

View file

@ -3,7 +3,9 @@
<%def name="title()">
%if c.repo_name:
${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
%elif c.repo_group_name:
${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
%else:
${_('Search inside all accessible repositories')}
%endif
@ -14,7 +16,9 @@
<%def name="breadcrumbs_links()">
%if c.repo_name:
${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
%elif c.repo_group_name:
${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
%else:
${_('Search inside all accessible repositories')}
%endif
@ -23,7 +27,9 @@
<%def name="menu_bar_nav()">
%if c.repo_name:
${self.menu_items(active='repositories')}
${self.menu_items(active='search')}
%elif c.repo_group_name:
${self.menu_items(active='search')}
%else:
${self.menu_items(active='search')}
%endif
@ -31,7 +37,9 @@
<%def name="menu_bar_subnav()">
%if c.repo_name:
${self.repo_menu(active='search')}
${self.repo_menu(active='search')}
%elif c.repo_group_name:
${self.repo_group_menu(active='search')}
%endif
</%def>
@ -43,6 +51,12 @@
${self.repo_page_title(c.rhodecode_db_repo)}
</div>
${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
%elif c.repo_group_name:
<!-- box / title -->
<div class="title">
${self.repo_group_page_title(c.repo_group)}
</div>
${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
%else:
<!-- box / title -->
<div class="title">
@ -54,13 +68,43 @@
%endif
<div class="form search-form">
<div class="fields">
${h.text('q', c.cur_query, placeholder="Enter query...")}
${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
${h.hidden('max_lines', '10')}
<input type="submit" value="${_('Search')}" class="btn"/>
<br/>
<div class="search-tags">
%if c.repo_name:
<span class="tag tag-ok disabled">
%if h.is_hg(c.rhodecode_db_repo):
<i class="icon-hg"></i>
%endif
%if h.is_git(c.rhodecode_db_repo):
<i class="icon-git"></i>
%endif
%if h.is_svn(c.rhodecode_db_repo):
<i class="icon-svn"></i>
%endif
${c.repo_name}
</span>
%elif c.repo_group_name:
<span class="tag tag-ok disabled">
<i class="icon-folder-close"></i>
${c.repo_group_name}
</span>
%endif
% for search_tag in c.search_tags:
<span class="tag tag-ok disabled">${search_tag}</span>
% endfor
</div>
<div class="search-feedback-items">
% for error in c.errors:
<span class="error-message">
@ -140,13 +184,18 @@ search type: ${handler.search_type_label}
</div>
<script>
$(document).ready(function(){
$('#q').autoGrowInput();
$("#id_search_type").select2({
'containerCssClass': "drop-menu",
'dropdownCssClass': "drop-menu-dropdown",
'dropdownAutoWidth': true,
'minimumResultsForSearch': -1
});
$('#q').autoGrowInput({maxWidth: 920});
setTimeout(function() {
$('#q').keyup()
}, 1);
})
</script>
</%def>

View file

@ -11,15 +11,18 @@
<div class="title">
${self.repo_page_title(c.rhodecode_db_repo)}
<ul class="links icon-only-links block-right">
<li>
%if c.rhodecode_user.username != h.DEFAULT_USER:
<a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
%else:
<a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
%endif
</li>
</ul>
## Context Action
<div>
<ul class="links icon-only-links block-right">
<li>
%if c.rhodecode_user.username != h.DEFAULT_USER:
<a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
%else:
<a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
%endif
</li>
</ul>
</div>
</div>
<div id="repo-summary" class="summary">