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(' |
|
128 | rmap.connect('goto_switcher_data', '/_goto_data', controller='home', | |
129 |
action=' |
|
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 |
|
197 | def goto_switcher_data(self): | |
167 | query = request.GET.get('query') |
|
198 | query = request.GET.get('query') | |
168 |
log.debug('generating switcher |
|
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 |
|
|
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(' |
|
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 Test |
|
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=' |
|
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=' |
|
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=' |
|
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_ |
|
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_ |
|
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_ |
|
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_ |
|
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