Show More
@@ -0,0 +1,26 b'' | |||||
|
1 | <%namespace name="base" file="/base/base.mako"/> | |||
|
2 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> | |||
|
3 | ||||
|
4 | % if c.can_view_pr: | |||
|
5 | <div class="pr-hovercard-header"> | |||
|
6 | <div class="pull-left tagtag"> | |||
|
7 | ${c.pull_request.status} | |||
|
8 | </div> | |||
|
9 | <div class="pr-hovercard-user"> | |||
|
10 | ${_('Created')}: ${h.format_date(c.pull_request.created_on)} | |||
|
11 | </div> | |||
|
12 | </div> | |||
|
13 | ||||
|
14 | <div class="pr-hovercard-title"> | |||
|
15 | <h3><a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">!${c.pull_request.pull_request_id}</a> - ${c.pull_request.title}</h3> | |||
|
16 | </div> | |||
|
17 | ||||
|
18 | <div class="pr-hovercard-footer"> | |||
|
19 | ${_('repo')}: ${c.pull_request.target_repo.repo_name} | |||
|
20 | </div> | |||
|
21 | % else: | |||
|
22 | ## user cannot view this PR we just show the generic info, without any exposed data | |||
|
23 | <div class="pr-hovercard-title"> | |||
|
24 | <h3>${_('Pull Request')} !${c.pull_request.pull_request_id}</h3> | |||
|
25 | </div> | |||
|
26 | % endif No newline at end of file |
@@ -30,6 +30,10 b' def includeme(config):' | |||||
30 | pattern='/_hovercard/user_group/{user_group_id}') |
|
30 | pattern='/_hovercard/user_group/{user_group_id}') | |
31 |
|
31 | |||
32 | config.add_route( |
|
32 | config.add_route( | |
|
33 | name='hovercard_pull_request', | |||
|
34 | pattern='/_hovercard/pull_request/{pull_request_id}') | |||
|
35 | ||||
|
36 | config.add_route( | |||
33 | name='hovercard_repo_commit', |
|
37 | name='hovercard_repo_commit', | |
34 | pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True) |
|
38 | pattern='/_hovercard/commit/{repo_name:.*?[^/]}/{commit_id}', repo_route=True) | |
35 |
|
39 |
@@ -35,7 +35,7 b' from rhodecode.lib.utils2 import safe_un' | |||||
35 | from rhodecode.lib.ext_json import json |
|
35 | from rhodecode.lib.ext_json import json | |
36 | from rhodecode.lib.vcs.nodes import FileNode |
|
36 | from rhodecode.lib.vcs.nodes import FileNode | |
37 | from rhodecode.model.db import ( |
|
37 | from rhodecode.model.db import ( | |
38 | func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup) |
|
38 | func, true, or_, case, in_filter_generator, Repository, RepoGroup, User, UserGroup, PullRequest) | |
39 | from rhodecode.model.repo import RepoModel |
|
39 | from rhodecode.model.repo import RepoModel | |
40 | from rhodecode.model.repo_group import RepoGroupModel |
|
40 | from rhodecode.model.repo_group import RepoGroupModel | |
41 | from rhodecode.model.scm import RepoGroupList, RepoList |
|
41 | from rhodecode.model.scm import RepoGroupList, RepoList | |
@@ -71,6 +71,19 b' class HoverCardsView(BaseAppView):' | |||||
71 | c.user_group = UserGroup.get_or_404(user_group_id) |
|
71 | c.user_group = UserGroup.get_or_404(user_group_id) | |
72 | return self._get_template_context(c) |
|
72 | return self._get_template_context(c) | |
73 |
|
73 | |||
|
74 | @LoginRequired() | |||
|
75 | @view_config( | |||
|
76 | route_name='hovercard_pull_request', request_method='GET', xhr=True, | |||
|
77 | renderer='rhodecode:templates/hovercards/hovercard_pull_request.mako') | |||
|
78 | def hovercard_pull_request(self): | |||
|
79 | c = self.load_default_context() | |||
|
80 | c.pull_request = PullRequest.get_or_404( | |||
|
81 | self.request.matchdict['pull_request_id']) | |||
|
82 | perms = ['repository.read', 'repository.write', 'repository.admin'] | |||
|
83 | c.can_view_pr = h.HasRepoPermissionAny(*perms)( | |||
|
84 | c.pull_request.target_repo.repo_name) | |||
|
85 | return self._get_template_context(c) | |||
|
86 | ||||
74 |
|
87 | |||
75 | class HoverCardsRepoView(RepoAppView): |
|
88 | class HoverCardsRepoView(RepoAppView): | |
76 | def load_default_context(self): |
|
89 | def load_default_context(self): |
@@ -74,9 +74,10 b' from webhelpers2.number import format_by' | |||||
74 | from rhodecode.lib.action_parser import action_parser |
|
74 | from rhodecode.lib.action_parser import action_parser | |
75 | from rhodecode.lib.ext_json import json |
|
75 | from rhodecode.lib.ext_json import json | |
76 | from rhodecode.lib.utils import repo_name_slug, get_custom_lexer |
|
76 | from rhodecode.lib.utils import repo_name_slug, get_custom_lexer | |
77 |
from rhodecode.lib.utils2 import |
|
77 | from rhodecode.lib.utils2 import ( | |
78 | get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \ |
|
78 | str2bool, safe_unicode, safe_str, | |
79 | AttributeDict, safe_int, md5, md5_safe |
|
79 | get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, | |
|
80 | AttributeDict, safe_int, md5, md5_safe, get_host_info) | |||
80 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links |
|
81 | from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links | |
81 | from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError |
|
82 | from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError | |
82 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit |
|
83 | from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit | |
@@ -1632,10 +1633,10 b' def _process_url_func(match_obj, repo_na' | |||||
1632 | '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">' |
|
1633 | '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">' | |
1633 | '%(issue-prefix)s%(id-repr)s' |
|
1634 | '%(issue-prefix)s%(id-repr)s' | |
1634 | '</a>') |
|
1635 | '</a>') | |
1635 |
elif link_format |
|
1636 | elif link_format in ['rst', 'rst+hovercard']: | |
1636 | tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' |
|
1637 | tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' | |
1637 |
elif link_format |
|
1638 | elif link_format in ['markdown', 'markdown+hovercard']: | |
1638 | tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)' |
|
1639 | tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)' | |
1639 | else: |
|
1640 | else: | |
1640 | raise ValueError('Bad link_format:{}'.format(link_format)) |
|
1641 | raise ValueError('Bad link_format:{}'.format(link_format)) | |
1641 |
|
1642 | |||
@@ -1648,11 +1649,23 b' def _process_url_func(match_obj, repo_na' | |||||
1648 | 'repo': repo_name, |
|
1649 | 'repo': repo_name, | |
1649 | 'repo_name': repo_name_cleaned, |
|
1650 | 'repo_name': repo_name_cleaned, | |
1650 | 'group_name': parent_group_name, |
|
1651 | 'group_name': parent_group_name, | |
|
1652 | # set dummy keys so we always have them | |||
|
1653 | 'hostname': '', | |||
|
1654 | 'netloc': '', | |||
|
1655 | 'scheme': '' | |||
1651 | } |
|
1656 | } | |
|
1657 | ||||
|
1658 | request = get_current_request() | |||
|
1659 | if request: | |||
|
1660 | # exposes, hostname, netloc, scheme | |||
|
1661 | host_data = get_host_info(request) | |||
|
1662 | named_vars.update(host_data) | |||
|
1663 | ||||
1652 | # named regex variables |
|
1664 | # named regex variables | |
1653 | named_vars.update(match_obj.groupdict()) |
|
1665 | named_vars.update(match_obj.groupdict()) | |
1654 | _url = string.Template(entry['url']).safe_substitute(**named_vars) |
|
1666 | _url = string.Template(entry['url']).safe_substitute(**named_vars) | |
1655 | desc = string.Template(entry['desc']).safe_substitute(**named_vars) |
|
1667 | desc = string.Template(entry['desc']).safe_substitute(**named_vars) | |
|
1668 | hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars) | |||
1656 |
|
1669 | |||
1657 | def quote_cleaner(input_str): |
|
1670 | def quote_cleaner(input_str): | |
1658 | """Remove quotes as it's HTML""" |
|
1671 | """Remove quotes as it's HTML""" | |
@@ -1666,8 +1679,9 b' def _process_url_func(match_obj, repo_na' | |||||
1666 | 'issue-prefix': entry['pref'], |
|
1679 | 'issue-prefix': entry['pref'], | |
1667 | 'serv': entry['url'], |
|
1680 | 'serv': entry['url'], | |
1668 | 'title': desc, |
|
1681 | 'title': desc, | |
1669 |
'hovercard_url': |
|
1682 | 'hovercard_url': hovercard_url | |
1670 | } |
|
1683 | } | |
|
1684 | ||||
1671 | if return_raw_data: |
|
1685 | if return_raw_data: | |
1672 | return { |
|
1686 | return { | |
1673 | 'id': issue_id, |
|
1687 | 'id': issue_id, | |
@@ -1690,7 +1704,8 b' def get_active_pattern_entries(repo_name' | |||||
1690 |
|
1704 | |||
1691 | def process_patterns(text_string, repo_name, link_format='html', active_entries=None): |
|
1705 | def process_patterns(text_string, repo_name, link_format='html', active_entries=None): | |
1692 |
|
1706 | |||
1693 |
allowed_formats = ['html', 'rst', 'markdown' |
|
1707 | allowed_formats = ['html', 'rst', 'markdown', | |
|
1708 | 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] | |||
1694 | if link_format not in allowed_formats: |
|
1709 | if link_format not in allowed_formats: | |
1695 | raise ValueError('Link format can be only one of:{} got {}'.format( |
|
1710 | raise ValueError('Link format can be only one of:{} got {}'.format( | |
1696 | allowed_formats, link_format)) |
|
1711 | allowed_formats, link_format)) | |
@@ -1732,13 +1747,16 b' def process_patterns(text_string, repo_n' | |||||
1732 |
|
1747 | |||
1733 | # finally use global replace, eg !123 -> pr-link, those will not catch |
|
1748 | # finally use global replace, eg !123 -> pr-link, those will not catch | |
1734 | # if already similar pattern exists |
|
1749 | # if already similar pattern exists | |
|
1750 | server_url = '${scheme}://${netloc}' | |||
1735 | pr_entry = { |
|
1751 | pr_entry = { | |
1736 | 'pref': '!', |
|
1752 | 'pref': '!', | |
1737 | 'url': '/_admin/pull-requests/${id}', |
|
1753 | 'url': server_url + '/_admin/pull-requests/${id}', | |
1738 | 'desc': 'Pull Request !${id}' |
|
1754 | 'desc': 'Pull Request !${id}', | |
|
1755 | 'hovercard_url': server_url + '/_hovercard/pull_request/${id}' | |||
1739 | } |
|
1756 | } | |
1740 | pr_url_func = partial( |
|
1757 | pr_url_func = partial( | |
1741 |
_process_url_func, repo_name=repo_name, entry=pr_entry, uid=None |
|
1758 | _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None, | |
|
1759 | link_format=link_format+'+hovercard') | |||
1742 | new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text) |
|
1760 | new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text) | |
1743 | log.debug('processed !pr pattern') |
|
1761 | log.debug('processed !pr pattern') | |
1744 |
|
1762 |
@@ -629,9 +629,29 b' def credentials_filter(uri):' | |||||
629 | return ''.join(uri) |
|
629 | return ''.join(uri) | |
630 |
|
630 | |||
631 |
|
631 | |||
|
632 | def get_host_info(request): | |||
|
633 | """ | |||
|
634 | Generate host info, to obtain full url e.g https://server.com | |||
|
635 | use this | |||
|
636 | `{scheme}://{netloc}` | |||
|
637 | """ | |||
|
638 | if not request: | |||
|
639 | return {} | |||
|
640 | ||||
|
641 | qualified_home_url = request.route_url('home') | |||
|
642 | parsed_url = urlobject.URLObject(qualified_home_url) | |||
|
643 | decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) | |||
|
644 | ||||
|
645 | return { | |||
|
646 | 'scheme': parsed_url.scheme, | |||
|
647 | 'netloc': parsed_url.netloc+decoded_path, | |||
|
648 | 'hostname': parsed_url.hostname, | |||
|
649 | } | |||
|
650 | ||||
|
651 | ||||
632 | def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override): |
|
652 | def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override): | |
633 | qualifed_home_url = request.route_url('home') |
|
653 | qualified_home_url = request.route_url('home') | |
634 | parsed_url = urlobject.URLObject(qualifed_home_url) |
|
654 | parsed_url = urlobject.URLObject(qualified_home_url) | |
635 | decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) |
|
655 | decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/'))) | |
636 |
|
656 | |||
637 | args = { |
|
657 | args = { |
@@ -27,6 +27,10 b' a { cursor: pointer; }' | |||||
27 | display: block; |
|
27 | display: block; | |
28 | } |
|
28 | } | |
29 |
|
29 | |||
|
30 | .clear-both { | |||
|
31 | clear: both; | |||
|
32 | } | |||
|
33 | ||||
30 | .pull-right { |
|
34 | .pull-right { | |
31 | float: right !important; |
|
35 | float: right !important; | |
32 | } |
|
36 | } |
@@ -2895,3 +2895,19 b' form.markup-form {' | |||||
2895 | clear: both; |
|
2895 | clear: both; | |
2896 | min-height: 10px; |
|
2896 | min-height: 10px; | |
2897 | } |
|
2897 | } | |
|
2898 | ||||
|
2899 | .pr-hovercard-header { | |||
|
2900 | clear: both; | |||
|
2901 | display: block; | |||
|
2902 | line-height: 20px; | |||
|
2903 | } | |||
|
2904 | ||||
|
2905 | .pr-hovercard-user { | |||
|
2906 | display: flex; | |||
|
2907 | align-items: center; | |||
|
2908 | padding-left: 5px; | |||
|
2909 | } | |||
|
2910 | ||||
|
2911 | .pr-hovercard-title { | |||
|
2912 | padding-top: 5px; | |||
|
2913 | } No newline at end of file |
@@ -32,6 +32,7 b' function registerRCRoutes() {' | |||||
32 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); |
|
32 | pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); | |
33 | pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']); |
|
33 | pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']); | |
34 | pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']); |
|
34 | pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']); | |
|
35 | pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']); | |||
35 | pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']); |
|
36 | pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']); | |
36 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); |
|
37 | pyroutes.register('ops_ping', '/_admin/ops/ping', []); | |
37 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); |
|
38 | pyroutes.register('ops_error_test', '/_admin/ops/error', []); |
General Comments 0
You need to be logged in to leave comments.
Login now