diff --git a/rhodecode/apps/home/__init__.py b/rhodecode/apps/home/__init__.py --- a/rhodecode/apps/home/__init__.py +++ b/rhodecode/apps/home/__init__.py @@ -33,5 +33,9 @@ def includeme(config): name='repo_list_data', pattern='/_repos') + config.add_route( + name='goto_switcher_data', + pattern='/_goto_data') + # Scan module for configuration decorators. config.scan() diff --git a/rhodecode/apps/home/tests/test_get_goto_switched_data.py b/rhodecode/apps/home/tests/test_get_goto_switched_data.py new file mode 100644 --- /dev/null +++ b/rhodecode/apps/home/tests/test_get_goto_switched_data.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2017 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import json + +import pytest + +from . import assert_and_get_content +from rhodecode.tests import TestController, TEST_USER_ADMIN_LOGIN +from rhodecode.tests.fixture import Fixture + +from rhodecode.lib.utils import map_groups +from rhodecode.model.repo import RepoModel +from rhodecode.model.repo_group import RepoGroupModel +from rhodecode.model.db import Session, Repository, RepoGroup + +fixture = Fixture() + + +def route_path(name, params=None, **kwargs): + import urllib + + base_url = { + 'goto_switcher_data': '/_goto_data', + }[name].format(**kwargs) + + if params: + base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) + return base_url + + +class TestGotoSwitcherData(TestController): + + required_repos_with_groups = [ + 'abc', + 'abc-fork', + 'forks/abcd', + 'abcd', + 'abcde', + 'a/abc', + 'aa/abc', + 'aaa/abc', + 'aaaa/abc', + 'repos_abc/aaa/abc', + 'abc_repos/abc', + 'abc_repos/abcd', + 'xxx/xyz', + 'forked-abc/a/abc' + ] + + @pytest.fixture(autouse=True, scope='class') + def prepare(self, request, pylonsapp): + for repo_and_group in self.required_repos_with_groups: + # create structure of groups and return the last group + + repo_group = map_groups(repo_and_group) + + RepoModel()._create_repo( + repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN, + repo_group=getattr(repo_group, 'group_id', None)) + + Session().commit() + + request.addfinalizer(self.cleanup) + + def cleanup(self): + # first delete all repos + for repo_and_groups in self.required_repos_with_groups: + repo = Repository.get_by_repo_name(repo_and_groups) + if repo: + RepoModel().delete(repo) + Session().commit() + + # then delete all empty groups + for repo_and_groups in self.required_repos_with_groups: + if '/' in repo_and_groups: + r_group = repo_and_groups.rsplit('/', 1)[0] + repo_group = RepoGroup.get_by_group_name(r_group) + if not repo_group: + continue + parents = repo_group.parents + RepoGroupModel().delete(repo_group, force_delete=True) + Session().commit() + + for el in reversed(parents): + RepoGroupModel().delete(el, force_delete=True) + Session().commit() + + def test_returns_list_of_repos_and_groups(self, xhr_header): + self.log_user() + + response = self.app.get( + route_path('goto_switcher_data'), + extra_environ=xhr_header, status=200) + result = json.loads(response.body)['results'] + + repos, groups, commits = assert_and_get_content(result) + + assert len(repos) == len(Repository.get_all()) + assert len(groups) == len(RepoGroup.get_all()) + assert len(commits) == 0 + + def test_returns_list_of_repos_and_groups_filtered(self, xhr_header): + self.log_user() + + response = self.app.get( + route_path('goto_switcher_data'), + params={'query': 'abc'}, + extra_environ=xhr_header, status=200) + result = json.loads(response.body)['results'] + + repos, groups, commits = assert_and_get_content(result) + + assert len(repos) == 13 + assert len(groups) == 5 + assert len(commits) == 0 + + def test_returns_list_of_properly_sorted_and_filtered(self, xhr_header): + self.log_user() + + response = self.app.get( + route_path('goto_switcher_data'), + params={'query': 'abc'}, + extra_environ=xhr_header, status=200) + result = json.loads(response.body)['results'] + + repos, groups, commits = assert_and_get_content(result) + + test_repos = [x['text'] for x in repos[:4]] + assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos + + test_groups = [x['text'] for x in groups[:4]] + assert ['abc_repos', 'repos_abc', + 'forked-abc', 'forked-abc/a'] == test_groups diff --git a/rhodecode/apps/home/views.py b/rhodecode/apps/home/views.py --- a/rhodecode/apps/home/views.py +++ b/rhodecode/apps/home/views.py @@ -18,6 +18,7 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ +import re import logging from pyramid.view import view_config @@ -25,8 +26,9 @@ from pyramid.view import view_config from rhodecode.apps._base import BaseAppView from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, NotAnonymous +from rhodecode.lib.index import searcher_from_config from rhodecode.lib.utils2 import safe_unicode, str2bool -from rhodecode.model.db import func, Repository +from rhodecode.model.db import func, Repository, RepoGroup from rhodecode.model.repo import RepoModel from rhodecode.model.scm import ScmModel @@ -111,6 +113,57 @@ class HomeView(BaseAppView): } for obj in repo_iter] + def _get_repo_group_list(self, name_contains=None, limit=20): + query = RepoGroup.query()\ + .order_by(func.length(RepoGroup.group_name))\ + .order_by(RepoGroup.group_name) + + if name_contains: + ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) + query = query.filter( + RepoGroup.group_name.ilike(ilike_expression)) + query = query.limit(limit) + + all_groups = query.all() + repo_groups_iter = ScmModel().get_repo_groups(all_groups) + return [ + { + 'id': obj.group_name, + 'text': obj.group_name, + 'type': 'group', + 'obj': {}, + 'url': h.url('repo_group_home', group_name=obj.group_name) + } + for obj in repo_groups_iter] + + def _get_hash_commit_list(self, auth_user, hash_starts_with=None): + if not hash_starts_with or len(hash_starts_with) < 3: + return [] + + commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with) + + if len(commit_hashes) != 1: + return [] + + commit_hash_prefix = commit_hashes[0] + + searcher = searcher_from_config(self.request.registry.settings) + result = searcher.search( + 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user, + raise_on_exc=False) + + return [ + { + 'id': entry['commit_id'], + 'text': entry['commit_id'], + 'type': 'commit', + 'obj': {'repo': entry['repository']}, + 'url': h.url('changeset_home', + repo_name=entry['repository'], + revision=entry['commit_id']) + } + for entry in result['results']] + @LoginRequired() @view_config( route_name='repo_list_data', request_method='GET', @@ -136,3 +189,49 @@ class HomeView(BaseAppView): 'results': res } return data + + @LoginRequired() + @view_config( + route_name='goto_switcher_data', request_method='GET', + renderer='json_ext', xhr=True) + def goto_switcher_data(self): + c = self.load_default_context() + + _ = self.request.translate + + query = self.request.GET.get('query') + log.debug('generating goto switcher list, query %s', query) + + res = [] + repo_groups = self._get_repo_group_list(query) + if repo_groups: + res.append({ + 'text': _('Groups'), + 'children': repo_groups + }) + + repos = self._get_repo_list(query) + if repos: + res.append({ + 'text': _('Repositories'), + 'children': repos + }) + + commits = self._get_hash_commit_list(c.auth_user, query) + if commits: + unique_repos = {} + for commit in commits: + unique_repos.setdefault(commit['obj']['repo'], [] + ).append(commit) + + for repo in unique_repos: + res.append({ + 'text': _('Commits in %(repo)s') % {'repo': repo}, + 'children': unique_repos[repo] + }) + + data = { + 'more': False, + 'results': res + } + return data diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -188,8 +188,6 @@ def make_map(config): # MAIN PAGE rmap.connect('home', '/', controller='home', action='index', jsroute=True) - rmap.connect('goto_switcher_data', '/_goto_data', controller='home', - action='goto_switcher_data') # TODO: johbo: Static links, to be replaced by our redirection mechanism rmap.connect('rst_help', diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py --- a/rhodecode/controllers/home.py +++ b/rhodecode/controllers/home.py @@ -24,20 +24,15 @@ Home controller for RhodeCode Enterprise import logging import time -import re -from pylons import tmpl_context as c, request, url, config -from pylons.i18n.translation import _ -from sqlalchemy.sql import func +from pylons import tmpl_context as c from rhodecode.lib.auth import ( - LoginRequired, HasPermissionAllDecorator, AuthUser, - HasRepoGroupPermissionAnyDecorator, XHRRequired) + LoginRequired, HasPermissionAllDecorator, + HasRepoGroupPermissionAnyDecorator) from rhodecode.lib.base import BaseController, render -from rhodecode.lib.index import searcher_from_config + from rhodecode.lib.ext_json import json -from rhodecode.lib.utils import jsonify -from rhodecode.lib.utils2 import safe_unicode, str2bool from rhodecode.model.db import Repository, RepoGroup from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel @@ -113,123 +108,3 @@ class HomeController(BaseController): c.repo_groups_data = json.dumps(repo_group_data) return render('index_repo_group.mako') - - def _get_repo_list(self, name_contains=None, repo_type=None, limit=20): - query = Repository.query()\ - .order_by(func.length(Repository.repo_name))\ - .order_by(Repository.repo_name) - - if repo_type: - query = query.filter(Repository.repo_type == repo_type) - - if name_contains: - ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) - query = query.filter( - Repository.repo_name.ilike(ilike_expression)) - query = query.limit(limit) - - all_repos = query.all() - repo_iter = self.scm_model.get_repos(all_repos) - return [ - { - 'id': obj['name'], - 'text': obj['name'], - 'type': 'repo', - 'obj': obj['dbrepo'], - 'url': url('summary_home', repo_name=obj['name']) - } - for obj in repo_iter] - - def _get_repo_group_list(self, name_contains=None, limit=20): - query = RepoGroup.query()\ - .order_by(func.length(RepoGroup.group_name))\ - .order_by(RepoGroup.group_name) - - if name_contains: - ilike_expression = u'%{}%'.format(safe_unicode(name_contains)) - query = query.filter( - RepoGroup.group_name.ilike(ilike_expression)) - query = query.limit(limit) - - all_groups = query.all() - repo_groups_iter = self.scm_model.get_repo_groups(all_groups) - return [ - { - 'id': obj.group_name, - 'text': obj.group_name, - 'type': 'group', - 'obj': {}, - 'url': url('repo_group_home', group_name=obj.group_name) - } - for obj in repo_groups_iter] - - def _get_hash_commit_list(self, hash_starts_with=None, limit=20): - if not hash_starts_with or len(hash_starts_with) < 3: - return [] - - commit_hashes = re.compile('([0-9a-f]{2,40})').findall(hash_starts_with) - - if len(commit_hashes) != 1: - return [] - - commit_hash_prefix = commit_hashes[0] - - auth_user = AuthUser( - user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr) - searcher = searcher_from_config(config) - result = searcher.search( - 'commit_id:%s*' % commit_hash_prefix, 'commit', auth_user, - raise_on_exc=False) - - return [ - { - 'id': entry['commit_id'], - 'text': entry['commit_id'], - 'type': 'commit', - 'obj': {'repo': entry['repository']}, - 'url': url('changeset_home', - repo_name=entry['repository'], - revision=entry['commit_id']) - } - for entry in result['results']] - - @LoginRequired() - @XHRRequired() - @jsonify - def goto_switcher_data(self): - query = request.GET.get('query') - log.debug('generating goto switcher list, query %s', query) - - res = [] - repo_groups = self._get_repo_group_list(query) - if repo_groups: - res.append({ - 'text': _('Groups'), - 'children': repo_groups - }) - - repos = self._get_repo_list(query) - if repos: - res.append({ - 'text': _('Repositories'), - 'children': repos - }) - - commits = self._get_hash_commit_list(query) - if commits: - unique_repos = {} - for commit in commits: - unique_repos.setdefault(commit['obj']['repo'], [] - ).append(commit) - - for repo in unique_repos: - res.append({ - 'text': _('Commits in %(repo)s') % {'repo': repo}, - 'children': unique_repos[repo] - }) - - data = { - 'more': False, - 'results': res - } - return data diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -96,6 +96,7 @@ function registerRCRoutes() { pyroutes.register('user_autocomplete_data', '/_users', []); pyroutes.register('user_group_autocomplete_data', '/_user_groups', []); pyroutes.register('repo_list_data', '/_repos', []); + pyroutes.register('goto_switcher_data', '/_goto_data', []); pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']); pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']); pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']); diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako --- a/rhodecode/templates/base/base.mako +++ b/rhodecode/templates/base/base.mako @@ -503,7 +503,7 @@ query.callback({results: cachedData.results}); } else { $.ajax({ - url: "${h.url('goto_switcher_data')}", + url: pyroutes.url('goto_switcher_data'), data: {'query': query.term}, dataType: 'json', type: 'GET', diff --git a/rhodecode/tests/functional/test_home.py b/rhodecode/tests/functional/test_home.py --- a/rhodecode/tests/functional/test_home.py +++ b/rhodecode/tests/functional/test_home.py @@ -18,20 +18,17 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -import json -from mock import patch import pytest from pylons import tmpl_context as c import rhodecode -from rhodecode.lib.utils import map_groups -from rhodecode.model.db import Repository, User, RepoGroup +from rhodecode.model.db import Repository, User from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.settings import SettingsModel -from rhodecode.tests import TestController, url, TEST_USER_ADMIN_LOGIN +from rhodecode.tests import TestController, url from rhodecode.tests.fixture import Fixture @@ -130,128 +127,3 @@ class TestHomeController(TestController) response.mustcontain(version_string) if state is False: response.mustcontain(no=[version_string]) - - -def assert_and_get_content(result): - repos = [] - groups = [] - commits = [] - for data in result: - for data_item in data['children']: - assert data_item['id'] - assert data_item['text'] - assert data_item['url'] - if data_item['type'] == 'repo': - repos.append(data_item) - elif data_item['type'] == 'group': - groups.append(data_item) - elif data_item['type'] == 'commit': - commits.append(data_item) - else: - raise Exception('invalid type %s' % data_item['type']) - - return repos, groups, commits - - -class TestGotoSwitcherData(TestController): - required_repos_with_groups = [ - 'abc', - 'abc-fork', - 'forks/abcd', - 'abcd', - 'abcde', - 'a/abc', - 'aa/abc', - 'aaa/abc', - 'aaaa/abc', - 'repos_abc/aaa/abc', - 'abc_repos/abc', - 'abc_repos/abcd', - 'xxx/xyz', - 'forked-abc/a/abc' - ] - - @pytest.fixture(autouse=True, scope='class') - def prepare(self, request, pylonsapp): - for repo_and_group in self.required_repos_with_groups: - # create structure of groups and return the last group - - repo_group = map_groups(repo_and_group) - - RepoModel()._create_repo( - repo_and_group, 'hg', 'test-ac', TEST_USER_ADMIN_LOGIN, - repo_group=getattr(repo_group, 'group_id', None)) - - Session().commit() - - request.addfinalizer(self.cleanup) - - def cleanup(self): - # first delete all repos - for repo_and_groups in self.required_repos_with_groups: - repo = Repository.get_by_repo_name(repo_and_groups) - if repo: - RepoModel().delete(repo) - Session().commit() - - # then delete all empty groups - for repo_and_groups in self.required_repos_with_groups: - if '/' in repo_and_groups: - r_group = repo_and_groups.rsplit('/', 1)[0] - repo_group = RepoGroup.get_by_group_name(r_group) - if not repo_group: - continue - parents = repo_group.parents - RepoGroupModel().delete(repo_group, force_delete=True) - Session().commit() - - for el in reversed(parents): - RepoGroupModel().delete(el, force_delete=True) - Session().commit() - - def test_returns_list_of_repos_and_groups(self): - self.log_user() - - response = self.app.get( - url(controller='home', action='goto_switcher_data'), - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, status=200) - result = json.loads(response.body)['results'] - - repos, groups, commits = assert_and_get_content(result) - - assert len(repos) == len(Repository.get_all()) - assert len(groups) == len(RepoGroup.get_all()) - assert len(commits) == 0 - - def test_returns_list_of_repos_and_groups_filtered(self): - self.log_user() - - response = self.app.get( - url(controller='home', action='goto_switcher_data'), - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, - params={'query': 'abc'}, status=200) - result = json.loads(response.body)['results'] - - repos, groups, commits = assert_and_get_content(result) - - assert len(repos) == 13 - assert len(groups) == 5 - assert len(commits) == 0 - - def test_returns_list_of_properly_sorted_and_filtered(self): - self.log_user() - - response = self.app.get( - url(controller='home', action='goto_switcher_data'), - headers={'X-REQUESTED-WITH': 'XMLHttpRequest', }, - params={'query': 'abc'}, status=200) - result = json.loads(response.body)['results'] - - repos, groups, commits = assert_and_get_content(result) - - test_repos = [x['text'] for x in repos[:4]] - assert ['abc', 'abcd', 'a/abc', 'abcde'] == test_repos - - test_groups = [x['text'] for x in groups[:4]] - assert ['abc_repos', 'repos_abc', - 'forked-abc', 'forked-abc/a'] == test_groups