diff --git a/rhodecode/apps/hovercards/__init__.py b/rhodecode/apps/hovercards/__init__.py --- a/rhodecode/apps/hovercards/__init__.py +++ b/rhodecode/apps/hovercards/__init__.py @@ -30,6 +30,10 @@ def includeme(config): pattern='/_hovercard/user_group/{user_group_id}') config.add_route( + name='hovercard_pull_request', + pattern='/_hovercard/pull_request/{pull_request_id}') + + config.add_route( name='hovercard_repo_commit', pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True) diff --git a/rhodecode/apps/hovercards/views.py b/rhodecode/apps/hovercards/views.py --- a/rhodecode/apps/hovercards/views.py +++ b/rhodecode/apps/hovercards/views.py @@ -35,7 +35,7 @@ from rhodecode.lib.utils2 import safe_un from rhodecode.lib.ext_json import json from rhodecode.lib.vcs.nodes import FileNode from rhodecode.model.db import ( - func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup) + func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup, PullRequest) from rhodecode.model.repo import RepoModel from rhodecode.model.repo_group import RepoGroupModel from rhodecode.model.scm import RepoGroupList, RepoList @@ -71,6 +71,19 @@ class HoverCardsView(BaseAppView): c.user_group = UserGroup.get_or_404(user_group_id) return self._get_template_context(c) + @LoginRequired() + @view_config( + route_name='hovercard_pull_request', request_method='GET', xhr=True, + renderer='rhodecode:templates/hovercards/hovercard_pull_request.mako') + def hovercard_pull_request(self): + c = self.load_default_context() + c.pull_request = PullRequest.get_or_404( + self.request.matchdict['pull_request_id']) + perms = ['repository.read', 'repository.write', 'repository.admin'] + c.can_view_pr = h.HasRepoPermissionAny(*perms)( + c.pull_request.target_repo.repo_name) + return self._get_template_context(c) + class HoverCardsRepoView(RepoAppView): def load_default_context(self): diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -74,9 +74,10 @@ from webhelpers2.number import format_by from rhodecode.lib.action_parser import action_parser from rhodecode.lib.ext_json import json from rhodecode.lib.utils import repo_name_slug, get_custom_lexer -from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \ - get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \ - AttributeDict, safe_int, md5, md5_safe +from rhodecode.lib.utils2 import ( + str2bool, safe_unicode, safe_str, + get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, + AttributeDict, safe_int, md5, md5_safe, get_host_info) from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit @@ -1632,10 +1633,10 @@ def _process_url_func(match_obj, repo_na '%(pref)s' '%(issue-prefix)s%(id-repr)s' '') - elif link_format == 'rst': + elif link_format in ['rst', 'rst+hovercard']: tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' - elif link_format == 'markdown': - tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)' + elif link_format in ['markdown', 'markdown+hovercard']: + tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)' else: raise ValueError('Bad link_format:{}'.format(link_format)) @@ -1648,11 +1649,23 @@ def _process_url_func(match_obj, repo_na 'repo': repo_name, 'repo_name': repo_name_cleaned, 'group_name': parent_group_name, + # set dummy keys so we always have them + 'hostname': '', + 'netloc': '', + 'scheme': '' } + + request = get_current_request() + if request: + # exposes, hostname, netloc, scheme + host_data = get_host_info(request) + named_vars.update(host_data) + # named regex variables named_vars.update(match_obj.groupdict()) _url = string.Template(entry['url']).safe_substitute(**named_vars) desc = string.Template(entry['desc']).safe_substitute(**named_vars) + hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars) def quote_cleaner(input_str): """Remove quotes as it's HTML""" @@ -1666,8 +1679,9 @@ def _process_url_func(match_obj, repo_na 'issue-prefix': entry['pref'], 'serv': entry['url'], 'title': desc, - 'hovercard_url': '' + 'hovercard_url': hovercard_url } + if return_raw_data: return { 'id': issue_id, @@ -1690,7 +1704,8 @@ def get_active_pattern_entries(repo_name def process_patterns(text_string, repo_name, link_format='html', active_entries=None): - allowed_formats = ['html', 'rst', 'markdown'] + allowed_formats = ['html', 'rst', 'markdown', + 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] if link_format not in allowed_formats: raise ValueError('Link format can be only one of:{} got {}'.format( allowed_formats, link_format)) @@ -1732,13 +1747,16 @@ def process_patterns(text_string, repo_n # finally use global replace, eg !123 -> pr-link, those will not catch # if already similar pattern exists + server_url = '${scheme}://${netloc}' pr_entry = { 'pref': '!', - 'url': '/_admin/pull-requests/${id}', - 'desc': 'Pull Request !${id}' + 'url': server_url + '/_admin/pull-requests/${id}', + 'desc': 'Pull Request !${id}', + 'hovercard_url': server_url + '/_hovercard/pull_request/${id}' } pr_url_func = partial( - _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None) + _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None, + link_format=link_format+'+hovercard') new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text) log.debug('processed !pr pattern') @@ -2009,7 +2027,7 @@ def get_last_path_part(file_node): def route_url(*args, **kwargs): """ - Wrapper around pyramids `route_url` (fully qualified url) function. + Wrapper around pyramids `route_url` (fully qualified url) function. """ req = get_current_request() return req.route_url(*args, **kwargs) diff --git a/rhodecode/lib/utils2.py b/rhodecode/lib/utils2.py --- a/rhodecode/lib/utils2.py +++ b/rhodecode/lib/utils2.py @@ -629,9 +629,29 @@ def credentials_filter(uri): return ''.join(uri) +def get_host_info(request): + """ + Generate host info, to obtain full url e.g https://server.com + use this + `{scheme}://{netloc}` + """ + if not request: + return {} + + qualified_home_url = request.route_url('home') + parsed_url = urlobject.URLObject(qualified_home_url) + decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) + + return { + 'scheme': parsed_url.scheme, + 'netloc': parsed_url.netloc+decoded_path, + 'hostname': parsed_url.hostname, + } + + def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override): - qualifed_home_url = request.route_url('home') - parsed_url = urlobject.URLObject(qualifed_home_url) + qualified_home_url = request.route_url('home') + parsed_url = urlobject.URLObject(qualified_home_url) decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) args = { diff --git a/rhodecode/public/css/helpers.less b/rhodecode/public/css/helpers.less --- a/rhodecode/public/css/helpers.less +++ b/rhodecode/public/css/helpers.less @@ -27,6 +27,10 @@ a { cursor: pointer; } display: block; } +.clear-both { + clear: both; +} + .pull-right { float: right !important; } diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -2895,3 +2895,19 @@ form.markup-form { clear: both; min-height: 10px; } + +.pr-hovercard-header { + clear: both; + display: block; + line-height: 20px; +} + +.pr-hovercard-user { + display: flex; + align-items: center; + padding-left: 5px; +} + +.pr-hovercard-title { + padding-top: 5px; +} \ No newline at end of file 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 @@ -32,6 +32,7 @@ function registerRCRoutes() { pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']); pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']); + pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']); pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']); pyroutes.register('ops_ping', '/_admin/ops/ping', []); pyroutes.register('ops_error_test', '/_admin/ops/error', []); diff --git a/rhodecode/templates/hovercards/hovercard_pull_request.mako b/rhodecode/templates/hovercards/hovercard_pull_request.mako new file mode 100644 --- /dev/null +++ b/rhodecode/templates/hovercards/hovercard_pull_request.mako @@ -0,0 +1,26 @@ +<%namespace name="base" file="/base/base.mako"/> +<%namespace name="dt" file="/data_table/_dt_elements.mako"/> + +% if c.can_view_pr: +
+
+ ${c.pull_request.status} +
+
+ ${_('Created')}: ${h.format_date(c.pull_request.created_on)} +
+
+ +
+

!${c.pull_request.pull_request_id} - ${c.pull_request.title}

+
+ + +% else: +## user cannot view this PR we just show the generic info, without any exposed data +
+

${_('Pull Request')} !${c.pull_request.pull_request_id}

+
+% endif \ No newline at end of file