##// END OF EJS Templates
feature: Go To switcher now searches commit hashes as well
dan -
r62:81398162 default
parent child Browse files
Show More
@@ -125,8 +125,8 b' def make_map(config):'
125
125
126 # MAIN PAGE
126 # MAIN PAGE
127 rmap.connect('home', '/', controller='home', action='index')
127 rmap.connect('home', '/', controller='home', action='index')
128 rmap.connect('repo_switcher_data', '/_repos_and_groups', controller='home',
128 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
129 action='repo_switcher_data')
129 action='goto_switcher_data')
130 rmap.connect('repo_list_data', '/_repos', controller='home',
130 rmap.connect('repo_list_data', '/_repos', controller='home',
131 action='repo_list_data')
131 action='repo_list_data')
132
132
@@ -24,16 +24,17 b' Home controller for RhodeCode Enterprise'
24
24
25 import logging
25 import logging
26 import time
26 import time
27
27 import re
28
28
29 from pylons import tmpl_context as c, request
29 from pylons import tmpl_context as c, request, url, config
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from sqlalchemy.sql import func
31 from sqlalchemy.sql import func
32
32
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasPermissionAllDecorator,
34 LoginRequired, HasPermissionAllDecorator, AuthUser,
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
35 HasRepoGroupPermissionAnyDecorator, XHRRequired)
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.index import searcher_from_config
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils import jsonify
39 from rhodecode.lib.utils2 import safe_unicode
40 from rhodecode.lib.utils2 import safe_unicode
@@ -134,7 +135,8 b' class HomeController(BaseController):'
134 'id': obj['name'],
135 'id': obj['name'],
135 'text': obj['name'],
136 'text': obj['name'],
136 'type': 'repo',
137 'type': 'repo',
137 'obj': obj['dbrepo']
138 'obj': obj['dbrepo'],
139 'url': url('summary_home', repo_name=obj['name'])
138 }
140 }
139 for obj in repo_iter]
141 for obj in repo_iter]
140
142
@@ -156,16 +158,45 b' class HomeController(BaseController):'
156 'id': obj.group_name,
158 'id': obj.group_name,
157 'text': obj.group_name,
159 'text': obj.group_name,
158 'type': 'group',
160 'type': 'group',
159 'obj': {}
161 'obj': {},
162 'url': url('repo_group_home', group_name=obj.group_name)
160 }
163 }
161 for obj in repo_groups_iter]
164 for obj in repo_groups_iter]
162
165
166 def _get_hash_commit_list(self, hash_starts_with=None, limit=20):
167 if not hash_starts_with or len(hash_starts_with) < 3:
168 return []
169
170 commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with)
171
172 if len(commit_hashes) != 1:
173 return []
174
175 commit_hash_prefix = commit_hashes[0]
176
177 auth_user = AuthUser(
178 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
179 searcher = searcher_from_config(config)
180 result = searcher.search(
181 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user)
182
183 return [
184 {
185 'id': entry['commit_id'],
186 'text': entry['commit_id'],
187 'type': 'commit',
188 'obj': {'repo': entry['repository']},
189 'url': url('changeset_home',
190 repo_name=entry['repository'], revision=entry['commit_id'])
191 }
192 for entry in result['results']]
193
163 @LoginRequired()
194 @LoginRequired()
164 @XHRRequired()
195 @XHRRequired()
165 @jsonify
196 @jsonify
166 def repo_switcher_data(self):
197 def goto_switcher_data(self):
167 query = request.GET.get('query')
198 query = request.GET.get('query')
168 log.debug('generating switcher repo/groups list, query %s', query)
199 log.debug('generating goto switcher list, query %s', query)
169
200
170 res = []
201 res = []
171 repo_groups = self._get_repo_group_list(query)
202 repo_groups = self._get_repo_group_list(query)
@@ -182,6 +213,19 b' class HomeController(BaseController):'
182 'children': repos
213 'children': repos
183 })
214 })
184
215
216 commits = self._get_hash_commit_list(query)
217 if commits:
218 unique_repos = {}
219 for commit in commits:
220 unique_repos.setdefault(commit['obj']['repo'], []
221 ).append(commit)
222
223 for repo in unique_repos:
224 res.append({
225 'text': _('Commits in %(repo)s') % {'repo': repo},
226 'children': unique_repos[repo]
227 })
228
185 data = {
229 data = {
186 'more': False,
230 'more': False,
187 'results': res
231 'results': res
@@ -203,6 +247,7 b' class HomeController(BaseController):'
203 'text': _('Repositories'),
247 'text': _('Repositories'),
204 'children': repos
248 'children': repos
205 })
249 })
250
206 data = {
251 data = {
207 'more': False,
252 'more': False,
208 'results': res
253 'results': res
@@ -42,7 +42,6 b' class BaseSearch(object):'
42 def search(self, query, document_type, search_user, repo_name=None):
42 def search(self, query, document_type, search_user, repo_name=None):
43 raise Exception('NotImplemented')
43 raise Exception('NotImplemented')
44
44
45
46 def searcher_from_config(config, prefix='search.'):
45 def searcher_from_config(config, prefix='search.'):
47 _config = {}
46 _config = {}
48 for key in config.keys():
47 for key in config.keys():
@@ -25,6 +25,7 b' Index schema for RhodeCode'
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26 import logging
26 import logging
27 import os
27 import os
28 import re
28
29
29 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
30
31
@@ -59,6 +60,7 b' FRAGMENTER = ContextFragmenter(200)'
59 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
60
61
61
62
63
62 class Search(BaseSearch):
64 class Search(BaseSearch):
63
65
64 name = 'whoosh'
66 name = 'whoosh'
@@ -90,8 +92,19 b' class Search(BaseSearch):'
90 if self.searcher:
92 if self.searcher:
91 self.searcher.close()
93 self.searcher.close()
92
94
95 def _extend_query(self, query):
96 hashes = re.compile('([0-9a-f]{5,40})').findall(query)
97 if hashes:
98 hashes_or_query = ' OR '.join('commit_id:%s*' % h for h in hashes)
99 query = u'(%s) OR %s' % (query, hashes_or_query)
100 return query
101
93 def search(self, query, document_type, search_user, repo_name=None,
102 def search(self, query, document_type, search_user, repo_name=None,
94 requested_page=1, page_limit=10):
103 requested_page=1, page_limit=10):
104
105 original_query = query
106 query = self._extend_query(query)
107
95 log.debug(u'QUERY: %s on %s', query, document_type)
108 log.debug(u'QUERY: %s on %s', query, document_type)
96 result = {
109 result = {
97 'results': [],
110 'results': [],
@@ -455,8 +455,11 b''
455 tmpl += '<i class="icon-unlock-alt"></i> ';
455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 }
456 }
457 }
457 }
458 if(obj_dict && state.type == 'commit') {
459 tmpl += '<i class="icon-tag"></i>';
460 }
458 if(obj_dict && state.type == 'group'){
461 if(obj_dict && state.type == 'group'){
459 tmpl += '<i class="icon-folder-close"></i> ';
462 tmpl += '<i class="icon-folder-close"></i> ';
460 }
463 }
461 tmpl += escapeMarkup(state.text);
464 tmpl += escapeMarkup(state.text);
462 return tmpl;
465 return tmpl;
@@ -496,7 +499,7 b''
496 query.callback({results: cachedData.results});
499 query.callback({results: cachedData.results});
497 } else {
500 } else {
498 $.ajax({
501 $.ajax({
499 url: "${h.url('repo_switcher_data')}",
502 url: "${h.url('goto_switcher_data')}",
500 data: {'query': query.term},
503 data: {'query': query.term},
501 dataType: 'json',
504 dataType: 'json',
502 type: 'GET',
505 type: 'GET',
@@ -514,7 +517,7 b''
514
517
515 $("#repo_switcher").on('select2-selecting', function(e){
518 $("#repo_switcher").on('select2-selecting', function(e){
516 e.preventDefault();
519 e.preventDefault();
517 window.location = pyroutes.url('summary_home', {'repo_name': e.val});
520 window.location = e.choice.url;
518 });
521 });
519
522
520 ## Global mouse bindings ##
523 ## Global mouse bindings ##
@@ -181,19 +181,25 b' class TestUserAutocompleteData(TestContr'
181 def assert_and_get_content(result):
181 def assert_and_get_content(result):
182 repos = []
182 repos = []
183 groups = []
183 groups = []
184 commits = []
184 for data in result:
185 for data in result:
185 for data_item in data['children']:
186 for data_item in data['children']:
186 assert data_item['id']
187 assert data_item['id']
187 assert data_item['text']
188 assert data_item['text']
189 assert data_item['url']
188 if data_item['type'] == 'repo':
190 if data_item['type'] == 'repo':
189 repos.append(data_item)
191 repos.append(data_item)
190 else:
192 elif data_item['type'] == 'group':
191 groups.append(data_item)
193 groups.append(data_item)
194 elif data_item['type'] == 'commit':
195 commits.append(data_item)
196 else:
197 raise Exception('invalid type %s' % data_item['type'])
192
198
193 return repos, groups
199 return repos, groups, commits
194
200
195
201
196 class TestRepoSwitcherData(TestController):
202 class TestGotoSwitcherData(TestController):
197 required_repos_with_groups = [
203 required_repos_with_groups = [
198 'abc',
204 'abc',
199 'abc-fork',
205 'abc-fork',
@@ -253,39 +259,41 b' class TestRepoSwitcherData(TestControlle'
253 self.log_user()
259 self.log_user()
254
260
255 response = self.app.get(
261 response = self.app.get(
256 url(controller='home', action='repo_switcher_data'),
262 url(controller='home', action='goto_switcher_data'),
257 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
263 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
258 result = json.loads(response.body)['results']
264 result = json.loads(response.body)['results']
259
265
260 repos, groups = assert_and_get_content(result)
266 repos, groups, commits = assert_and_get_content(result)
261
267
262 assert len(repos) == len(Repository.get_all())
268 assert len(repos) == len(Repository.get_all())
263 assert len(groups) == len(RepoGroup.get_all())
269 assert len(groups) == len(RepoGroup.get_all())
270 assert len(commits) == 0
264
271
265 def test_returns_list_of_repos_and_groups_filtered(self):
272 def test_returns_list_of_repos_and_groups_filtered(self):
266 self.log_user()
273 self.log_user()
267
274
268 response = self.app.get(
275 response = self.app.get(
269 url(controller='home', action='repo_switcher_data'),
276 url(controller='home', action='goto_switcher_data'),
270 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
277 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
271 params={'query': 'abc'}, status=200)
278 params={'query': 'abc'}, status=200)
272 result = json.loads(response.body)['results']
279 result = json.loads(response.body)['results']
273
280
274 repos, groups = assert_and_get_content(result)
281 repos, groups, commits = assert_and_get_content(result)
275
282
276 assert len(repos) == 13
283 assert len(repos) == 13
277 assert len(groups) == 5
284 assert len(groups) == 5
285 assert len(commits) == 0
278
286
279 def test_returns_list_of_properly_sorted_and_filtered(self):
287 def test_returns_list_of_properly_sorted_and_filtered(self):
280 self.log_user()
288 self.log_user()
281
289
282 response = self.app.get(
290 response = self.app.get(
283 url(controller='home', action='repo_switcher_data'),
291 url(controller='home', action='goto_switcher_data'),
284 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
292 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
285 params={'query': 'abc'}, status=200)
293 params={'query': 'abc'}, status=200)
286 result = json.loads(response.body)['results']
294 result = json.loads(response.body)['results']
287
295
288 repos, groups = assert_and_get_content(result)
296 repos, groups, commits = assert_and_get_content(result)
289
297
290 test_repos = [x['text'] for x in repos[:4]]
298 test_repos = [x['text'] for x in repos[:4]]
291 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
299 assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos
@@ -300,54 +308,58 b' class TestRepoListData(TestController):'
300 self.log_user()
308 self.log_user()
301
309
302 response = self.app.get(
310 response = self.app.get(
303 url(controller='home', action='repo_switcher_data'),
311 url(controller='home', action='repo_list_data'),
304 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
312 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200)
305 result = json.loads(response.body)['results']
313 result = json.loads(response.body)['results']
306
314
307 repos, groups = assert_and_get_content(result)
315 repos, groups, commits = assert_and_get_content(result)
308
316
309 assert len(repos) == len(Repository.get_all())
317 assert len(repos) == len(Repository.get_all())
310 assert len(groups) == 0
318 assert len(groups) == 0
319 assert len(commits) == 0
311
320
312 def test_returns_list_of_repos_and_groups_filtered(self):
321 def test_returns_list_of_repos_and_groups_filtered(self):
313 self.log_user()
322 self.log_user()
314
323
315 response = self.app.get(
324 response = self.app.get(
316 url(controller='home', action='repo_switcher_data'),
325 url(controller='home', action='repo_list_data'),
317 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
326 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
318 params={'query': 'vcs_test_git'}, status=200)
327 params={'query': 'vcs_test_git'}, status=200)
319 result = json.loads(response.body)['results']
328 result = json.loads(response.body)['results']
320
329
321 repos, groups = assert_and_get_content(result)
330 repos, groups, commits = assert_and_get_content(result)
322
331
323 assert len(repos) == len(Repository.query().filter(
332 assert len(repos) == len(Repository.query().filter(
324 Repository.repo_name.ilike('%vcs_test_git%')).all())
333 Repository.repo_name.ilike('%vcs_test_git%')).all())
325 assert len(groups) == 0
334 assert len(groups) == 0
335 assert len(commits) == 0
326
336
327 def test_returns_list_of_repos_and_groups_filtered_with_type(self):
337 def test_returns_list_of_repos_and_groups_filtered_with_type(self):
328 self.log_user()
338 self.log_user()
329
339
330 response = self.app.get(
340 response = self.app.get(
331 url(controller='home', action='repo_switcher_data'),
341 url(controller='home', action='repo_list_data'),
332 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
342 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
333 params={'query': 'vcs_test_git', 'repo_type': 'git'}, status=200)
343 params={'query': 'vcs_test_git', 'repo_type': 'git'}, status=200)
334 result = json.loads(response.body)['results']
344 result = json.loads(response.body)['results']
335
345
336 repos, groups = assert_and_get_content(result)
346 repos, groups, commits = assert_and_get_content(result)
337
347
338 assert len(repos) == len(Repository.query().filter(
348 assert len(repos) == len(Repository.query().filter(
339 Repository.repo_name.ilike('%vcs_test_git%')).all())
349 Repository.repo_name.ilike('%vcs_test_git%')).all())
340 assert len(groups) == 0
350 assert len(groups) == 0
351 assert len(commits) == 0
341
352
342 def test_returns_list_of_repos_non_ascii_query(self):
353 def test_returns_list_of_repos_non_ascii_query(self):
343 self.log_user()
354 self.log_user()
344 response = self.app.get(
355 response = self.app.get(
345 url(controller='home', action='repo_switcher_data'),
356 url(controller='home', action='repo_list_data'),
346 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
357 headers={'X-REQUESTED-WITH': 'XMLHttpRequest', },
347 params={'query': 'ć_vcs_test_ą', 'repo_type': 'git'}, status=200)
358 params={'query': 'ć_vcs_test_ą', 'repo_type': 'git'}, status=200)
348 result = json.loads(response.body)['results']
359 result = json.loads(response.body)['results']
349
360
350 repos, groups = assert_and_get_content(result)
361 repos, groups, commits = assert_and_get_content(result)
351
362
352 assert len(repos) == 0
363 assert len(repos) == 0
353 assert len(groups) == 0
364 assert len(groups) == 0
365 assert len(commits) == 0
@@ -129,6 +129,10 b' class TestSearchController(TestControlle'
129 ('author:marcin@python-blog.com '
129 ('author:marcin@python-blog.com '
130 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
130 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
131 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
131 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
132 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
133 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
134 ('b986218b', 1, [
135 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
132 ])
136 ])
133 def test_search_commit_messages(
137 def test_search_commit_messages(
134 self, query, expected_hits, expected_commits, enabled_backends):
138 self, query, expected_hits, expected_commits, enabled_backends):
General Comments 0
You need to be logged in to leave comments. Login now