Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,18 b'' | |||
|
1 | diff -rup Beaker-1.9.1-orig/beaker/session.py Beaker-1.9.1/beaker/session.py | |
|
2 | --- Beaker-1.9.1-orig/beaker/session.py 2020-04-10 10:23:04.000000000 +0200 | |
|
3 | +++ Beaker-1.9.1/beaker/session.py 2020-04-10 10:23:34.000000000 +0200 | |
|
4 | @@ -156,6 +156,14 @@ def __init__(self, request, id=None, invalidate_corrupt=False, | |
|
5 | if timeout and not save_accessed_time: | |
|
6 | raise BeakerException("timeout requires save_accessed_time") | |
|
7 | self.timeout = timeout | |
|
8 | + # We want to pass timeout param to redis backend to support expiration of keys | |
|
9 | + # In future, I believe, we can use this param for memcached and mongo as well | |
|
10 | + if self.timeout is not None and self.type == 'ext:redis': | |
|
11 | + # The backend expiration should always be a bit longer (I decied to use 2 minutes) than the | |
|
12 | + # session expiration itself to prevent the case where the backend data expires while | |
|
13 | + # the session is being read (PR#153) | |
|
14 | + self.namespace_args['timeout'] = self.timeout + 60 * 2 | |
|
15 | + | |
|
16 | self.save_atime = save_accessed_time | |
|
17 | self.use_cookies = use_cookies | |
|
18 | self.cookie_expires = cookie_expires No newline at end of file |
@@ -0,0 +1,26 b'' | |||
|
1 | diff -rup Beaker-1.9.1-orig/beaker/ext/redisnm.py Beaker-1.9.1/beaker/ext/redisnm.py | |
|
2 | --- Beaker-1.9.1-orig/beaker/ext/redisnm.py 2018-04-10 10:23:04.000000000 +0200 | |
|
3 | +++ Beaker-1.9.1/beaker/ext/redisnm.py 2018-04-10 10:23:34.000000000 +0200 | |
|
4 | @@ -30,9 +30,10 @@ class RedisNamespaceManager(NamespaceManager): | |
|
5 | ||
|
6 | clients = SyncDict() | |
|
7 | ||
|
8 | - def __init__(self, namespace, url, **kw): | |
|
9 | + def __init__(self, namespace, url, timeout=None, **kw): | |
|
10 | super(RedisNamespaceManager, self).__init__(namespace) | |
|
11 | self.lock_dir = None # Redis uses redis itself for locking. | |
|
12 | + self.timeout = timeout | |
|
13 | ||
|
14 | if redis is None: | |
|
15 | raise RuntimeError('redis is not available') | |
|
16 | @@ -68,6 +69,8 @@ def has_key(self, key): | |
|
17 | ||
|
18 | def set_value(self, key, value, expiretime=None): | |
|
19 | value = pickle.dumps(value) | |
|
20 | + if expiretime is None and self.timeout is not None: | |
|
21 | + expiretime = self.timeout | |
|
22 | if expiretime is not None: | |
|
23 | self.client.setex(self._format_key(key), int(expiretime), value) | |
|
24 | else: | |
|
25 | ||
|
26 |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -0,0 +1,52 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | import logging | |
|
4 | from sqlalchemy import * | |
|
5 | ||
|
6 | from alembic.migration import MigrationContext | |
|
7 | from alembic.operations import Operations | |
|
8 | ||
|
9 | from rhodecode.lib.dbmigrate.versions import _reset_base | |
|
10 | from rhodecode.model import meta, init_model_encryption | |
|
11 | ||
|
12 | ||
|
13 | log = logging.getLogger(__name__) | |
|
14 | ||
|
15 | ||
|
16 | def upgrade(migrate_engine): | |
|
17 | """ | |
|
18 | Upgrade operations go here. | |
|
19 | Don't create your own engine; bind migrate_engine to your metadata | |
|
20 | """ | |
|
21 | _reset_base(migrate_engine) | |
|
22 | from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db | |
|
23 | ||
|
24 | init_model_encryption(db) | |
|
25 | ||
|
26 | context = MigrationContext.configure(migrate_engine.connect()) | |
|
27 | op = Operations(context) | |
|
28 | ||
|
29 | table = db.PullRequestReviewers.__table__ | |
|
30 | with op.batch_alter_table(table.name) as batch_op: | |
|
31 | new_column = Column('role', Unicode(255), nullable=True) | |
|
32 | batch_op.add_column(new_column) | |
|
33 | ||
|
34 | _fill_reviewers_role(db, op, meta.Session) | |
|
35 | ||
|
36 | ||
|
37 | def downgrade(migrate_engine): | |
|
38 | meta = MetaData() | |
|
39 | meta.bind = migrate_engine | |
|
40 | ||
|
41 | ||
|
42 | def fixups(models, _SESSION): | |
|
43 | pass | |
|
44 | ||
|
45 | ||
|
46 | def _fill_reviewers_role(models, op, session): | |
|
47 | params = {'role': 'reviewer'} | |
|
48 | query = text( | |
|
49 | 'UPDATE pull_request_reviewers SET role = :role' | |
|
50 | ).bindparams(**params) | |
|
51 | op.execute(query) | |
|
52 | session().commit() |
@@ -0,0 +1,142 b'' | |||
|
1 | ## snippet for sidebar elements | |
|
2 | ## usage: | |
|
3 | ## <%namespace name="sidebar" file="/base/sidebar.mako"/> | |
|
4 | ## ${sidebar.comments_table()} | |
|
5 | <%namespace name="base" file="/base/base.mako"/> | |
|
6 | ||
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> | |
|
8 | <% | |
|
9 | if todo_comments: | |
|
10 | cls_ = 'todos-content-table' | |
|
11 | def sorter(entry): | |
|
12 | user_id = entry.author.user_id | |
|
13 | resolved = '1' if entry.resolved else '0' | |
|
14 | if user_id == c.rhodecode_user.user_id: | |
|
15 | # own comments first | |
|
16 | user_id = 0 | |
|
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
|
18 | else: | |
|
19 | cls_ = 'comments-content-table' | |
|
20 | def sorter(entry): | |
|
21 | user_id = entry.author.user_id | |
|
22 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |
|
23 | ||
|
24 | existing_ids = existing_ids or [] | |
|
25 | ||
|
26 | %> | |
|
27 | ||
|
28 | <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}"> | |
|
29 | ||
|
30 | % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))): | |
|
31 | <% | |
|
32 | display = '' | |
|
33 | _cls = '' | |
|
34 | %> | |
|
35 | ||
|
36 | <% | |
|
37 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) | |
|
38 | prev_comment_ver_index = 0 | |
|
39 | if loop_obj.previous: | |
|
40 | prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', [])) | |
|
41 | ||
|
42 | ver_info = None | |
|
43 | if getattr(c, 'versions', []): | |
|
44 | ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None | |
|
45 | %> | |
|
46 | <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %> | |
|
47 | <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %> | |
|
48 | <% | |
|
49 | if (prev_comment_ver_index > comment_ver_index): | |
|
50 | comments_ver_divider = comment_ver_index | |
|
51 | else: | |
|
52 | comments_ver_divider = None | |
|
53 | %> | |
|
54 | ||
|
55 | % if todo_comments: | |
|
56 | % if comment_obj.resolved: | |
|
57 | <% _cls = 'resolved-todo' %> | |
|
58 | <% display = 'none' %> | |
|
59 | % endif | |
|
60 | % else: | |
|
61 | ## SKIP TODOs we display them in other area | |
|
62 | % if comment_obj.is_todo: | |
|
63 | <% display = 'none' %> | |
|
64 | % endif | |
|
65 | ## Skip outdated comments | |
|
66 | % if comment_obj.outdated: | |
|
67 | <% display = 'none' %> | |
|
68 | <% _cls = 'hidden-comment' %> | |
|
69 | % endif | |
|
70 | % endif | |
|
71 | ||
|
72 | % if not todo_comments and comments_ver_divider: | |
|
73 | <tr class="old-comments-marker"> | |
|
74 | <td colspan="3"> | |
|
75 | % if ver_info: | |
|
76 | <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code> | |
|
77 | % else: | |
|
78 | <code>v${comments_ver_divider}</code> | |
|
79 | % endif | |
|
80 | </td> | |
|
81 | </tr> | |
|
82 | ||
|
83 | % endif | |
|
84 | ||
|
85 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> | |
|
86 | <td class="td-todo-number"> | |
|
87 | <% | |
|
88 | version_info = '' | |
|
89 | if is_pr: | |
|
90 | version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version') | |
|
91 | %> | |
|
92 | ||
|
93 | <script type="text/javascript"> | |
|
94 | // closure function helper | |
|
95 | var sidebarComment${comment_obj.comment_id} = function() { | |
|
96 | return renderTemplate('sideBarCommentHovercard', { | |
|
97 | version_info: "${version_info}", | |
|
98 | file_name: "${comment_obj.f_path}", | |
|
99 | line_no: "${comment_obj.line_no}", | |
|
100 | outdated: ${h.json.dumps(comment_obj.outdated)}, | |
|
101 | inline: ${h.json.dumps(comment_obj.is_inline)}, | |
|
102 | is_todo: ${h.json.dumps(comment_obj.is_todo)}, | |
|
103 | created_on: "${h.format_date(comment_obj.created_on)}", | |
|
104 | datetime: "${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}", | |
|
105 | review_status: "${(comment_obj.review_status or '')}" | |
|
106 | }) | |
|
107 | } | |
|
108 | </script> | |
|
109 | ||
|
110 | % if comment_obj.outdated: | |
|
111 | <i class="icon-comment-toggle tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
112 | % elif comment_obj.is_inline: | |
|
113 | <i class="icon-code tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
114 | % else: | |
|
115 | <i class="icon-comment tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
116 | % endif | |
|
117 | ||
|
118 | ## NEW, since refresh | |
|
119 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |
|
120 | <span class="tag">NEW</span> | |
|
121 | % endif | |
|
122 | </td> | |
|
123 | ||
|
124 | <td class="td-todo-gravatar"> | |
|
125 | ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])} | |
|
126 | </td> | |
|
127 | <td class="todo-comment-text-wrapper"> | |
|
128 | <div class="todo-comment-text ${('todo-resolved' if comment_obj.resolved else '')}"> | |
|
129 | <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink" | |
|
130 | href="#comment-${comment_obj.comment_id}" | |
|
131 | onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})"> | |
|
132 | ||
|
133 | ${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')} | |
|
134 | </a> | |
|
135 | </div> | |
|
136 | </td> | |
|
137 | </tr> | |
|
138 | % endfor | |
|
139 | ||
|
140 | </table> | |
|
141 | ||
|
142 | </%def> No newline at end of file |
@@ -1,6 +1,5 b'' | |||
|
1 | 1 | [bumpversion] |
|
2 |
current_version = 4.2 |
|
|
2 | current_version = 4.21.0 | |
|
3 | 3 | message = release: Bump version {current_version} to {new_version} |
|
4 | 4 | |
|
5 | 5 | [bumpversion:file:rhodecode/VERSION] |
|
6 |
@@ -5,25 +5,20 b' done = false' | |||
|
5 | 5 | done = true |
|
6 | 6 | |
|
7 | 7 | [task:rc_tools_pinned] |
|
8 | done = true | |
|
9 | 8 | |
|
10 | 9 | [task:fixes_on_stable] |
|
11 | done = true | |
|
12 | 10 | |
|
13 | 11 | [task:pip2nix_generated] |
|
14 | done = true | |
|
15 | 12 | |
|
16 | 13 | [task:changelog_updated] |
|
17 | done = true | |
|
18 | 14 | |
|
19 | 15 | [task:generate_api_docs] |
|
20 | done = true | |
|
16 | ||
|
17 | [task:updated_translation] | |
|
21 | 18 | |
|
22 | 19 | [release] |
|
23 |
state = |
|
|
24 |
version = 4.2 |
|
|
25 | ||
|
26 | [task:updated_translation] | |
|
20 | state = in_progress | |
|
21 | version = 4.21.0 | |
|
27 | 22 | |
|
28 | 23 | [task:generate_js_routes] |
|
29 | 24 |
@@ -147,12 +147,13 b' Use the following example to configure N' | |||
|
147 | 147 | |
|
148 | 148 | ## Special Cache for file store, make sure you enable this intentionally as |
|
149 | 149 | ## it could bypass upload files permissions |
|
150 | # location /_file_store/download { | |
|
150 | # location /_file_store/download/gravatars { | |
|
151 | 151 | # |
|
152 | 152 | # proxy_cache cache_zone; |
|
153 | 153 | # # ignore Set-Cookie |
|
154 | 154 | # proxy_ignore_headers Set-Cookie; |
|
155 |
# |
|
|
155 | # # ignore cache-control | |
|
156 | # proxy_ignore_headers Cache-Control; | |
|
156 | 157 | # |
|
157 | 158 | # proxy_cache_key $host$uri$is_args$args; |
|
158 | 159 | # proxy_cache_methods GET; |
@@ -32,6 +32,8 b' self: super: {' | |||
|
32 | 32 | patches = [ |
|
33 | 33 | ./patches/beaker/patch-beaker-lock-func-debug.diff |
|
34 | 34 | ./patches/beaker/patch-beaker-metadata-reuse.diff |
|
35 | ./patches/beaker/patch-beaker-improved-redis.diff | |
|
36 | ./patches/beaker/patch-beaker-improved-redis-2.diff | |
|
35 | 37 | ]; |
|
36 | 38 | }); |
|
37 | 39 |
@@ -35,6 +35,20 b' self: super: {' | |||
|
35 | 35 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
36 | 36 | }; |
|
37 | 37 | }; |
|
38 | "apispec" = super.buildPythonPackage { | |
|
39 | name = "apispec-1.0.0"; | |
|
40 | doCheck = false; | |
|
41 | propagatedBuildInputs = [ | |
|
42 | self."PyYAML" | |
|
43 | ]; | |
|
44 | src = fetchurl { | |
|
45 | url = "https://files.pythonhosted.org/packages/67/15/346c04988dd67d36007e28145504c520491930c878b1f484a97b27a8f497/apispec-1.0.0.tar.gz"; | |
|
46 | sha256 = "1712w1anvqrvadjjpvai84vbaygaxabd3zz5lxihdzwzs4gvi9sp"; | |
|
47 | }; | |
|
48 | meta = { | |
|
49 | license = [ pkgs.lib.licenses.mit ]; | |
|
50 | }; | |
|
51 | }; | |
|
38 | 52 | "appenlight-client" = super.buildPythonPackage { |
|
39 | 53 | name = "appenlight-client-0.6.26"; |
|
40 | 54 | doCheck = false; |
@@ -236,20 +250,23 b' self: super: {' | |||
|
236 | 250 | }; |
|
237 | 251 | }; |
|
238 | 252 | "channelstream" = super.buildPythonPackage { |
|
239 |
name = "channelstream-0. |
|
|
253 | name = "channelstream-0.6.14"; | |
|
240 | 254 | doCheck = false; |
|
241 | 255 | propagatedBuildInputs = [ |
|
242 | 256 | self."gevent" |
|
243 | 257 | self."ws4py" |
|
258 | self."marshmallow" | |
|
259 | self."python-dateutil" | |
|
244 | 260 | self."pyramid" |
|
245 | 261 | self."pyramid-jinja2" |
|
262 | self."pyramid-apispec" | |
|
246 | 263 | self."itsdangerous" |
|
247 | 264 | self."requests" |
|
248 | 265 | self."six" |
|
249 | 266 | ]; |
|
250 | 267 | src = fetchurl { |
|
251 |
url = "https://files.pythonhosted.org/packages/ |
|
|
252 | sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm"; | |
|
268 | url = "https://files.pythonhosted.org/packages/d4/2d/86d6757ccd06ce673ee224123471da3d45251d061da7c580bfc259bad853/channelstream-0.6.14.tar.gz"; | |
|
269 | sha256 = "0qgy5j3rj6c8cslzidh32glhkrhbbdxjc008y69v8a0y3zyaz2d3"; | |
|
253 | 270 | }; |
|
254 | 271 | meta = { |
|
255 | 272 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
@@ -862,11 +879,11 b' self: super: {' | |||
|
862 | 879 | }; |
|
863 | 880 | }; |
|
864 | 881 | "itsdangerous" = super.buildPythonPackage { |
|
865 |
name = "itsdangerous-0 |
|
|
882 | name = "itsdangerous-1.1.0"; | |
|
866 | 883 | doCheck = false; |
|
867 | 884 | src = fetchurl { |
|
868 |
url = "https://files.pythonhosted.org/packages/dc |
|
|
869 | sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb"; | |
|
885 | url = "https://files.pythonhosted.org/packages/68/1a/f27de07a8a304ad5fa817bbe383d1238ac4396da447fa11ed937039fa04b/itsdangerous-1.1.0.tar.gz"; | |
|
886 | sha256 = "068zpbksq5q2z4dckh2k1zbcq43ay74ylqn77rni797j0wyh66rj"; | |
|
870 | 887 | }; |
|
871 | 888 | meta = { |
|
872 | 889 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
@@ -993,6 +1010,17 b' self: super: {' | |||
|
993 | 1010 | license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ]; |
|
994 | 1011 | }; |
|
995 | 1012 | }; |
|
1013 | "marshmallow" = super.buildPythonPackage { | |
|
1014 | name = "marshmallow-2.18.0"; | |
|
1015 | doCheck = false; | |
|
1016 | src = fetchurl { | |
|
1017 | url = "https://files.pythonhosted.org/packages/ad/0b/5799965d1c6d5f608d684e2c0dce8a828e0309a3bfe8327d9418a89f591c/marshmallow-2.18.0.tar.gz"; | |
|
1018 | sha256 = "1g0aafpjn7yaxq06yndy8c7rs9n42adxkqq1ayhlr869pr06d3lm"; | |
|
1019 | }; | |
|
1020 | meta = { | |
|
1021 | license = [ pkgs.lib.licenses.mit ]; | |
|
1022 | }; | |
|
1023 | }; | |
|
996 | 1024 | "mistune" = super.buildPythonPackage { |
|
997 | 1025 | name = "mistune-0.8.4"; |
|
998 | 1026 | doCheck = false; |
@@ -1522,6 +1550,20 b' self: super: {' | |||
|
1522 | 1550 | license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; |
|
1523 | 1551 | }; |
|
1524 | 1552 | }; |
|
1553 | "pyramid-apispec" = super.buildPythonPackage { | |
|
1554 | name = "pyramid-apispec-0.3.2"; | |
|
1555 | doCheck = false; | |
|
1556 | propagatedBuildInputs = [ | |
|
1557 | self."apispec" | |
|
1558 | ]; | |
|
1559 | src = fetchurl { | |
|
1560 | url = "https://files.pythonhosted.org/packages/2a/30/1dea5d81ea635449572ba60ec3148310d75ae4530c3c695f54b0991bb8c7/pyramid_apispec-0.3.2.tar.gz"; | |
|
1561 | sha256 = "0ffrcqp9dkykivhfcq0v9lgy6w0qhwl6x78925vfjmayly9r8da0"; | |
|
1562 | }; | |
|
1563 | meta = { | |
|
1564 | license = [ pkgs.lib.licenses.bsdOriginal ]; | |
|
1565 | }; | |
|
1566 | }; | |
|
1525 | 1567 | "pyramid-mailer" = super.buildPythonPackage { |
|
1526 | 1568 | name = "pyramid-mailer-0.15.1"; |
|
1527 | 1569 | doCheck = false; |
@@ -1763,6 +1805,17 b' self: super: {' | |||
|
1763 | 1805 | license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ]; |
|
1764 | 1806 | }; |
|
1765 | 1807 | }; |
|
1808 | "PyYAML" = super.buildPythonPackage { | |
|
1809 | name = "PyYAML-5.3.1"; | |
|
1810 | doCheck = false; | |
|
1811 | src = fetchurl { | |
|
1812 | url = "https://files.pythonhosted.org/packages/64/c2/b80047c7ac2478f9501676c988a5411ed5572f35d1beff9cae07d321512c/PyYAML-5.3.1.tar.gz"; | |
|
1813 | sha256 = "0pb4zvkfxfijkpgd1b86xjsqql97ssf1knbd1v53wkg1qm9cgsmq"; | |
|
1814 | }; | |
|
1815 | meta = { | |
|
1816 | license = [ pkgs.lib.licenses.mit ]; | |
|
1817 | }; | |
|
1818 | }; | |
|
1766 | 1819 | "redis" = super.buildPythonPackage { |
|
1767 | 1820 | name = "redis-3.4.1"; |
|
1768 | 1821 | doCheck = false; |
@@ -1819,7 +1872,7 b' self: super: {' | |||
|
1819 | 1872 | }; |
|
1820 | 1873 | }; |
|
1821 | 1874 | "rhodecode-enterprise-ce" = super.buildPythonPackage { |
|
1822 |
name = "rhodecode-enterprise-ce-4.20. |
|
|
1875 | name = "rhodecode-enterprise-ce-4.20.0"; | |
|
1823 | 1876 | buildInputs = [ |
|
1824 | 1877 | self."pytest" |
|
1825 | 1878 | self."py" |
@@ -5,7 +5,7 b' babel==1.3' | |||
|
5 | 5 | beaker==1.9.1 |
|
6 | 6 | bleach==3.1.3 |
|
7 | 7 | celery==4.3.0 |
|
8 |
channelstream==0. |
|
|
8 | channelstream==0.6.14 | |
|
9 | 9 | click==7.0 |
|
10 | 10 | colander==1.7.0 |
|
11 | 11 | # our custom configobj |
@@ -22,7 +22,7 b' future==0.14.3' | |||
|
22 | 22 | futures==3.0.2 |
|
23 | 23 | infrae.cache==1.0.1 |
|
24 | 24 | iso8601==0.1.12 |
|
25 |
itsdangerous==0 |
|
|
25 | itsdangerous==1.1.0 | |
|
26 | 26 | kombu==4.6.6 |
|
27 | 27 | lxml==4.2.5 |
|
28 | 28 | mako==1.1.0 |
@@ -18,10 +18,11 b' jsonschema==2.6.0' | |||
|
18 | 18 | pluggy==0.13.1 |
|
19 | 19 | pyasn1-modules==0.2.6 |
|
20 | 20 | pyramid-jinja2==2.7 |
|
21 | pyramid-apispec==0.3.2 | |
|
21 | 22 | scandir==1.10.0 |
|
22 | 23 | setproctitle==1.1.10 |
|
23 | 24 | tempita==0.5.2 |
|
24 | 25 | testpath==0.4.4 |
|
25 | 26 | transaction==2.4.0 |
|
26 | 27 | vine==1.3.0 |
|
27 | wcwidth==0.1.9 | |
|
28 | wcwidth==0.1.9 No newline at end of file |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||
|
48 | 48 | EXTENSIONS = {} |
|
49 | 49 | |
|
50 | 50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
51 |
__dbversion__ = 10 |
|
|
51 | __dbversion__ = 109 # defines current db version for migrations | |
|
52 | 52 | __platform__ = platform.system() |
|
53 | 53 | __license__ = 'AGPLv3, and Commercial License' |
|
54 | 54 | __author__ = 'RhodeCode GmbH' |
@@ -170,8 +170,7 b' def validate_repo_permissions(apiuser, r' | |||
|
170 | 170 | """ |
|
171 | 171 | if not HasRepoPermissionAnyApi(*perms)( |
|
172 | 172 | user=apiuser, repo_name=repo.repo_name): |
|
173 | raise JSONRPCError( | |
|
174 | 'repository `%s` does not exist' % repoid) | |
|
173 | raise JSONRPCError('repository `%s` does not exist' % repoid) | |
|
175 | 174 | |
|
176 | 175 | return True |
|
177 | 176 |
@@ -307,8 +307,7 b' def get_repo_changeset(request, apiuser,' | |||
|
307 | 307 | """ |
|
308 | 308 | repo = get_repo_or_error(repoid) |
|
309 | 309 | if not has_superadmin_permission(apiuser): |
|
310 | _perms = ( | |
|
311 | 'repository.admin', 'repository.write', 'repository.read',) | |
|
310 | _perms = ('repository.admin', 'repository.write', 'repository.read',) | |
|
312 | 311 | validate_repo_permissions(apiuser, repoid, repo, _perms) |
|
313 | 312 | |
|
314 | 313 | changes_details = Optional.extract(details) |
@@ -366,8 +365,7 b' def get_repo_changesets(request, apiuser' | |||
|
366 | 365 | """ |
|
367 | 366 | repo = get_repo_or_error(repoid) |
|
368 | 367 | if not has_superadmin_permission(apiuser): |
|
369 | _perms = ( | |
|
370 | 'repository.admin', 'repository.write', 'repository.read',) | |
|
368 | _perms = ('repository.admin', 'repository.write', 'repository.read',) | |
|
371 | 369 | validate_repo_permissions(apiuser, repoid, repo, _perms) |
|
372 | 370 | |
|
373 | 371 | changes_details = Optional.extract(details) |
@@ -1021,7 +1019,8 b' def update_repo(' | |||
|
1021 | 1019 | |
|
1022 | 1020 | include_secrets = False |
|
1023 | 1021 | if not has_superadmin_permission(apiuser): |
|
1024 |
|
|
|
1022 | _perms = ('repository.admin',) | |
|
1023 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |
|
1025 | 1024 | else: |
|
1026 | 1025 | include_secrets = True |
|
1027 | 1026 | |
@@ -1208,8 +1207,7 b' def fork_repo(request, apiuser, repoid, ' | |||
|
1208 | 1207 | if not has_superadmin_permission(apiuser): |
|
1209 | 1208 | # check if we have at least read permission for |
|
1210 | 1209 | # this repo that we fork ! |
|
1211 | _perms = ( | |
|
1212 | 'repository.admin', 'repository.write', 'repository.read') | |
|
1210 | _perms = ('repository.admin', 'repository.write', 'repository.read') | |
|
1213 | 1211 | validate_repo_permissions(apiuser, repoid, repo, _perms) |
|
1214 | 1212 | |
|
1215 | 1213 | # check if the regular user has at least fork permissions as well |
@@ -2370,12 +2368,13 b' def get_repo_settings(request, apiuser, ' | |||
|
2370 | 2368 | } |
|
2371 | 2369 | """ |
|
2372 | 2370 | |
|
2373 | # Restrict access to this api method to admins only. | |
|
2371 | # Restrict access to this api method to super-admins, and repo admins only. | |
|
2372 | repo = get_repo_or_error(repoid) | |
|
2374 | 2373 | if not has_superadmin_permission(apiuser): |
|
2375 | raise JSONRPCForbidden() | |
|
2374 | _perms = ('repository.admin',) | |
|
2375 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |
|
2376 | 2376 | |
|
2377 | 2377 | try: |
|
2378 | repo = get_repo_or_error(repoid) | |
|
2379 | 2378 | settings_model = VcsSettingsModel(repo=repo) |
|
2380 | 2379 | settings = settings_model.get_global_settings() |
|
2381 | 2380 | settings.update(settings_model.get_repo_settings()) |
@@ -2414,9 +2413,11 b' def set_repo_settings(request, apiuser, ' | |||
|
2414 | 2413 | "result": true |
|
2415 | 2414 | } |
|
2416 | 2415 | """ |
|
2417 | # Restrict access to this api method to admins only. | |
|
2416 | # Restrict access to this api method to super-admins, and repo admins only. | |
|
2417 | repo = get_repo_or_error(repoid) | |
|
2418 | 2418 | if not has_superadmin_permission(apiuser): |
|
2419 | raise JSONRPCForbidden() | |
|
2419 | _perms = ('repository.admin',) | |
|
2420 | validate_repo_permissions(apiuser, repoid, repo, _perms) | |
|
2420 | 2421 | |
|
2421 | 2422 | if type(settings) is not dict: |
|
2422 | 2423 | raise JSONRPCError('Settings have to be a JSON Object.') |
@@ -34,7 +34,7 b' from rhodecode.lib.channelstream import ' | |||
|
34 | 34 | get_user_data, |
|
35 | 35 | parse_channels_info, |
|
36 | 36 | update_history_from_logs, |
|
37 | STATE_PUBLIC_KEYS) | |
|
37 | USER_STATE_PUBLIC_KEYS) | |
|
38 | 38 | |
|
39 | 39 | from rhodecode.lib.auth import NotAnonymous |
|
40 | 40 | |
@@ -86,14 +86,16 b' class ChannelstreamView(BaseAppView):' | |||
|
86 | 86 | 'display_name': None, |
|
87 | 87 | 'display_link': None, |
|
88 | 88 | } |
|
89 | user_data['permissions'] = self._rhodecode_user.permissions_safe | |
|
89 | ||
|
90 | #user_data['permissions'] = self._rhodecode_user.permissions_safe | |
|
91 | ||
|
90 | 92 | payload = { |
|
91 | 93 | 'username': user.username, |
|
92 | 94 | 'user_state': user_data, |
|
93 | 95 | 'conn_id': str(uuid.uuid4()), |
|
94 | 96 | 'channels': channels, |
|
95 | 97 | 'channel_configs': {}, |
|
96 | 'state_public_keys': STATE_PUBLIC_KEYS, | |
|
98 | 'state_public_keys': USER_STATE_PUBLIC_KEYS, | |
|
97 | 99 | 'info': { |
|
98 | 100 | 'exclude_channels': ['broadcast'] |
|
99 | 101 | } |
@@ -118,10 +120,13 b' class ChannelstreamView(BaseAppView):' | |||
|
118 | 120 | 'Channelstream service at {} is down'.format(channelstream_url)) |
|
119 | 121 | return HTTPBadGateway() |
|
120 | 122 | |
|
123 | channel_info = connect_result.get('channels_info') | |
|
124 | if not channel_info: | |
|
125 | raise HTTPBadRequest() | |
|
126 | ||
|
121 | 127 | connect_result['channels'] = channels |
|
122 | 128 | connect_result['channels_info'] = parse_channels_info( |
|
123 | connect_result['channels_info'], | |
|
124 | include_channel_info=filtered_channels) | |
|
129 | channel_info, include_channel_info=filtered_channels) | |
|
125 | 130 | update_history_from_logs(self.channelstream_config, |
|
126 | 131 | filtered_channels, connect_result) |
|
127 | 132 | return connect_result |
@@ -167,10 +172,15 b' class ChannelstreamView(BaseAppView):' | |||
|
167 | 172 | log.exception( |
|
168 | 173 | 'Channelstream service at {} is down'.format(channelstream_url)) |
|
169 | 174 | return HTTPBadGateway() |
|
175 | ||
|
176 | channel_info = connect_result.get('channels_info') | |
|
177 | if not channel_info: | |
|
178 | raise HTTPBadRequest() | |
|
179 | ||
|
170 | 180 | # include_channel_info will limit history only to new channel |
|
171 | 181 | # to not overwrite histories on other channels in client |
|
172 | 182 | connect_result['channels_info'] = parse_channels_info( |
|
173 |
|
|
|
183 | channel_info, | |
|
174 | 184 | include_channel_info=filtered_channels) |
|
175 | 185 | update_history_from_logs( |
|
176 | 186 | self.channelstream_config, filtered_channels, connect_result) |
@@ -43,10 +43,10 b' def includeme(config):' | |||
|
43 | 43 | pattern='/_file_store/upload') |
|
44 | 44 | config.add_route( |
|
45 | 45 | name='download_file', |
|
46 | pattern='/_file_store/download/{fid}') | |
|
46 | pattern='/_file_store/download/{fid:.*}') | |
|
47 | 47 | config.add_route( |
|
48 | 48 | name='download_file_by_token', |
|
49 | pattern='/_file_store/token-download/{_auth_token}/{fid}') | |
|
49 | pattern='/_file_store/token-download/{_auth_token}/{fid:.*}') | |
|
50 | 50 | |
|
51 | 51 | # Scan module for configuration decorators. |
|
52 | 52 | config.scan('.views', ignore='.tests') |
@@ -20,6 +20,7 b'' | |||
|
20 | 20 | |
|
21 | 21 | import os |
|
22 | 22 | import time |
|
23 | import errno | |
|
23 | 24 | import shutil |
|
24 | 25 | import hashlib |
|
25 | 26 | |
@@ -32,9 +33,24 b' from rhodecode.apps.file_store.exception' | |||
|
32 | 33 | METADATA_VER = 'v1' |
|
33 | 34 | |
|
34 | 35 | |
|
36 | def safe_make_dirs(dir_path): | |
|
37 | if not os.path.exists(dir_path): | |
|
38 | try: | |
|
39 | os.makedirs(dir_path) | |
|
40 | except OSError as e: | |
|
41 | if e.errno != errno.EEXIST: | |
|
42 | raise | |
|
43 | return | |
|
44 | ||
|
45 | ||
|
35 | 46 | class LocalFileStorage(object): |
|
36 | 47 | |
|
37 | 48 | @classmethod |
|
49 | def apply_counter(cls, counter, filename): | |
|
50 | name_counted = '%d-%s' % (counter, filename) | |
|
51 | return name_counted | |
|
52 | ||
|
53 | @classmethod | |
|
38 | 54 | def resolve_name(cls, name, directory): |
|
39 | 55 | """ |
|
40 | 56 | Resolves a unique name and the correct path. If a filename |
@@ -47,17 +63,16 b' class LocalFileStorage(object):' | |||
|
47 | 63 | |
|
48 | 64 | counter = 0 |
|
49 | 65 | while True: |
|
50 |
name = |
|
|
66 | name_counted = cls.apply_counter(counter, name) | |
|
51 | 67 | |
|
52 | 68 | # sub_store prefix to optimize disk usage, e.g some_path/ab/final_file |
|
53 | sub_store = cls._sub_store_from_filename(name) | |
|
69 | sub_store = cls._sub_store_from_filename(name_counted) | |
|
54 | 70 | sub_store_path = os.path.join(directory, sub_store) |
|
55 |
|
|
|
56 | os.makedirs(sub_store_path) | |
|
71 | safe_make_dirs(sub_store_path) | |
|
57 | 72 | |
|
58 | path = os.path.join(sub_store_path, name) | |
|
73 | path = os.path.join(sub_store_path, name_counted) | |
|
59 | 74 | if not os.path.exists(path): |
|
60 | return name, path | |
|
75 | return name_counted, path | |
|
61 | 76 | counter += 1 |
|
62 | 77 | |
|
63 | 78 | @classmethod |
@@ -102,8 +117,13 b' class LocalFileStorage(object):' | |||
|
102 | 117 | |
|
103 | 118 | :param filename: base name of file |
|
104 | 119 | """ |
|
120 | prefix_dir = '' | |
|
121 | if '/' in filename: | |
|
122 | prefix_dir, filename = filename.split('/') | |
|
105 | 123 | sub_store = self._sub_store_from_filename(filename) |
|
106 | return os.path.join(self.base_path, sub_store, filename) | |
|
124 | else: | |
|
125 | sub_store = self._sub_store_from_filename(filename) | |
|
126 | return os.path.join(self.base_path, prefix_dir, sub_store, filename) | |
|
107 | 127 | |
|
108 | 128 | def delete(self, filename): |
|
109 | 129 | """ |
@@ -123,7 +143,7 b' class LocalFileStorage(object):' | |||
|
123 | 143 | Checks if file exists. Resolves filename's absolute |
|
124 | 144 | path based on base_path. |
|
125 | 145 | |
|
126 | :param filename: base name of file | |
|
146 | :param filename: file_uid name of file, e.g 0-f62b2b2d-9708-4079-a071-ec3f958448d4.svg | |
|
127 | 147 | """ |
|
128 | 148 | return os.path.exists(self.store_path(filename)) |
|
129 | 149 | |
@@ -158,7 +178,7 b' class LocalFileStorage(object):' | |||
|
158 | 178 | return ext in [normalize_ext(x) for x in extensions] |
|
159 | 179 | |
|
160 | 180 | def save_file(self, file_obj, filename, directory=None, extensions=None, |
|
161 | extra_metadata=None, max_filesize=None, **kwargs): | |
|
181 | extra_metadata=None, max_filesize=None, randomized_name=True, **kwargs): | |
|
162 | 182 | """ |
|
163 | 183 | Saves a file object to the uploads location. |
|
164 | 184 | Returns the resolved filename, i.e. the directory + |
@@ -169,6 +189,7 b' class LocalFileStorage(object):' | |||
|
169 | 189 | :param directory: relative path of sub-directory |
|
170 | 190 | :param extensions: iterable of allowed extensions, if not default |
|
171 | 191 | :param max_filesize: maximum size of file that should be allowed |
|
192 | :param randomized_name: generate random generated UID or fixed based on the filename | |
|
172 | 193 | :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix |
|
173 | 194 | |
|
174 | 195 | """ |
@@ -183,13 +204,12 b' class LocalFileStorage(object):' | |||
|
183 | 204 | else: |
|
184 | 205 | dest_directory = self.base_path |
|
185 | 206 | |
|
186 |
|
|
|
187 | os.makedirs(dest_directory) | |
|
207 | safe_make_dirs(dest_directory) | |
|
188 | 208 | |
|
189 | filename = utils.uid_filename(filename) | |
|
209 | uid_filename = utils.uid_filename(filename, randomized=randomized_name) | |
|
190 | 210 | |
|
191 | 211 | # resolve also produces special sub-dir for file optimized store |
|
192 | filename, path = self.resolve_name(filename, dest_directory) | |
|
212 | filename, path = self.resolve_name(uid_filename, dest_directory) | |
|
193 | 213 | stored_file_dir = os.path.dirname(path) |
|
194 | 214 | |
|
195 | 215 | file_obj.seek(0) |
@@ -210,12 +230,13 b' class LocalFileStorage(object):' | |||
|
210 | 230 | |
|
211 | 231 | file_hash = self.calculate_path_hash(path) |
|
212 | 232 | |
|
213 | metadata.update( | |
|
214 |
|
|
|
233 | metadata.update({ | |
|
234 | "filename": filename, | |
|
215 | 235 | "size": size, |
|
216 | 236 | "time": time.time(), |
|
217 | 237 | "sha256": file_hash, |
|
218 |
"meta_ver": METADATA_VER |
|
|
238 | "meta_ver": METADATA_VER | |
|
239 | }) | |
|
219 | 240 | |
|
220 | 241 | filename_meta = filename + '.meta' |
|
221 | 242 | with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta: |
@@ -20,7 +20,7 b'' | |||
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import uuid |
|
23 | ||
|
23 | import StringIO | |
|
24 | 24 | import pathlib2 |
|
25 | 25 | |
|
26 | 26 | |
@@ -52,3 +52,7 b' def uid_filename(filename, randomized=Tr' | |||
|
52 | 52 | hash_key = '{}.{}'.format(filename, 'store') |
|
53 | 53 | uid = uuid.uuid5(uuid.NAMESPACE_URL, hash_key) |
|
54 | 54 | return str(uid) + ext.lower() |
|
55 | ||
|
56 | ||
|
57 | def bytes_to_file_obj(bytes_data): | |
|
58 | return StringIO.StringIO(bytes_data) |
@@ -64,7 +64,7 b' class FileStoreView(BaseAppView):' | |||
|
64 | 64 | file_uid, store_path) |
|
65 | 65 | raise HTTPNotFound() |
|
66 | 66 | |
|
67 |
db_obj = FileStore( |
|
|
67 | db_obj = FileStore.get_by_store_uid(file_uid, safe=True) | |
|
68 | 68 | if not db_obj: |
|
69 | 69 | raise HTTPNotFound() |
|
70 | 70 |
@@ -345,6 +345,16 b' def includeme(config):' | |||
|
345 | 345 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete', |
|
346 | 346 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
347 | 347 | |
|
348 | config.add_route( | |
|
349 | name='pullrequest_comments', | |
|
350 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments', | |
|
351 | repo_route=True) | |
|
352 | ||
|
353 | config.add_route( | |
|
354 | name='pullrequest_todos', | |
|
355 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos', | |
|
356 | repo_route=True) | |
|
357 | ||
|
348 | 358 | # Artifacts, (EE feature) |
|
349 | 359 | config.add_route( |
|
350 | 360 | name='repo_artifacts_list', |
@@ -485,23 +485,10 b' class TestRepoCommitCommentsView(TestCon' | |||
|
485 | 485 | |
|
486 | 486 | |
|
487 | 487 | def assert_comment_links(response, comments, inline_comments): |
|
488 | if comments == 1: | |
|
489 | comments_text = "%d General" % comments | |
|
490 | else: | |
|
491 | comments_text = "%d General" % comments | |
|
492 | ||
|
493 | if inline_comments == 1: | |
|
494 | inline_comments_text = "%d Inline" % inline_comments | |
|
495 | else: | |
|
496 | inline_comments_text = "%d Inline" % inline_comments | |
|
488 | response.mustcontain( | |
|
489 | '<span class="display-none" id="general-comments-count">{}</span>'.format(comments)) | |
|
490 | response.mustcontain( | |
|
491 | '<span class="display-none" id="inline-comments-count">{}</span>'.format(inline_comments)) | |
|
497 | 492 | |
|
498 | if comments: | |
|
499 | response.mustcontain('<a href="#comments">%s</a>,' % comments_text) | |
|
500 | else: | |
|
501 | response.mustcontain(comments_text) | |
|
502 | 493 | |
|
503 | if inline_comments: | |
|
504 | response.mustcontain( | |
|
505 | 'id="inline-comments-counter">%s' % inline_comments_text) | |
|
506 | else: | |
|
507 | response.mustcontain(inline_comments_text) | |
|
494 |
@@ -619,7 +619,12 b' class ComparePage(AssertResponse):' | |||
|
619 | 619 | self.contains_one_anchor(file_id) |
|
620 | 620 | diffblock = doc.cssselect('[data-f-path="%s"]' % filename) |
|
621 | 621 | assert len(diffblock) == 2 |
|
622 |
|
|
|
622 | for lnk in diffblock[0].cssselect('a'): | |
|
623 | if 'permalink' in lnk.text: | |
|
624 | assert '#{}'.format(file_id) in lnk.attrib['href'] | |
|
625 | break | |
|
626 | else: | |
|
627 | pytest.fail('Unable to find permalink') | |
|
623 | 628 | |
|
624 | 629 | def contains_change_summary(self, files_changed, inserted, deleted): |
|
625 | 630 | template = ( |
@@ -150,9 +150,9 b' class TestPullrequestsView(object):' | |||
|
150 | 150 | response = self.app.post( |
|
151 | 151 | route_path('pullrequest_create', repo_name=source.repo_name), |
|
152 | 152 | [ |
|
153 |
('source_repo', source |
|
|
153 | ('source_repo', source_repo_name), | |
|
154 | 154 | ('source_ref', source_ref), |
|
155 |
('target_repo', target |
|
|
155 | ('target_repo', target_repo_name), | |
|
156 | 156 | ('target_ref', target_ref), |
|
157 | 157 | ('common_ancestor', commit_ids['initial-commit']), |
|
158 | 158 | ('pullrequest_title', 'Title'), |
@@ -1110,16 +1110,17 b' class TestPullrequestsView(object):' | |||
|
1110 | 1110 | |
|
1111 | 1111 | # source has ancestor - change - change-2 |
|
1112 | 1112 | backend.pull_heads(source, heads=['change-2']) |
|
1113 | target_repo_name = target.repo_name | |
|
1113 | 1114 | |
|
1114 | 1115 | # update PR |
|
1115 | 1116 | self.app.post( |
|
1116 | 1117 | route_path('pullrequest_update', |
|
1117 |
repo_name=target |
|
|
1118 | repo_name=target_repo_name, pull_request_id=pull_request_id), | |
|
1118 | 1119 | params={'update_commits': 'true', 'csrf_token': csrf_token}) |
|
1119 | 1120 | |
|
1120 | 1121 | response = self.app.get( |
|
1121 | 1122 | route_path('pullrequest_show', |
|
1122 |
repo_name=target |
|
|
1123 | repo_name=target_repo_name, | |
|
1123 | 1124 | pull_request_id=pull_request.pull_request_id)) |
|
1124 | 1125 | |
|
1125 | 1126 | assert response.status_int == 200 |
@@ -1166,10 +1167,11 b' class TestPullrequestsView(object):' | |||
|
1166 | 1167 | # source has ancestor - ancestor-new - change-rebased |
|
1167 | 1168 | backend.pull_heads(target, heads=['ancestor-new']) |
|
1168 | 1169 | backend.pull_heads(source, heads=['change-rebased']) |
|
1170 | target_repo_name = target.repo_name | |
|
1169 | 1171 | |
|
1170 | 1172 | # update PR |
|
1171 | 1173 | url = route_path('pullrequest_update', |
|
1172 |
repo_name=target |
|
|
1174 | repo_name=target_repo_name, | |
|
1173 | 1175 | pull_request_id=pull_request_id) |
|
1174 | 1176 | self.app.post(url, |
|
1175 | 1177 | params={'update_commits': 'true', 'csrf_token': csrf_token}, |
@@ -1183,7 +1185,7 b' class TestPullrequestsView(object):' | |||
|
1183 | 1185 | |
|
1184 | 1186 | response = self.app.get( |
|
1185 | 1187 | route_path('pullrequest_show', |
|
1186 |
repo_name=target |
|
|
1188 | repo_name=target_repo_name, | |
|
1187 | 1189 | pull_request_id=pull_request.pull_request_id)) |
|
1188 | 1190 | assert response.status_int == 200 |
|
1189 | 1191 | response.mustcontain('Pull request updated to') |
@@ -1232,16 +1234,17 b' class TestPullrequestsView(object):' | |||
|
1232 | 1234 | vcsrepo = target.scm_instance() |
|
1233 | 1235 | vcsrepo.config.clear_section('hooks') |
|
1234 | 1236 | vcsrepo.run_git_command(['reset', '--soft', 'HEAD~2']) |
|
1237 | target_repo_name = target.repo_name | |
|
1235 | 1238 | |
|
1236 | 1239 | # update PR |
|
1237 | 1240 | url = route_path('pullrequest_update', |
|
1238 |
repo_name=target |
|
|
1241 | repo_name=target_repo_name, | |
|
1239 | 1242 | pull_request_id=pull_request_id) |
|
1240 | 1243 | self.app.post(url, |
|
1241 | 1244 | params={'update_commits': 'true', 'csrf_token': csrf_token}, |
|
1242 | 1245 | status=200) |
|
1243 | 1246 | |
|
1244 |
response = self.app.get(route_path('pullrequest_new', repo_name=target |
|
|
1247 | response = self.app.get(route_path('pullrequest_new', repo_name=target_repo_name)) | |
|
1245 | 1248 | assert response.status_int == 200 |
|
1246 | 1249 | response.mustcontain('Pull request updated to') |
|
1247 | 1250 | response.mustcontain('with 0 added, 0 removed commits.') |
@@ -1280,11 +1283,12 b' class TestPullrequestsView(object):' | |||
|
1280 | 1283 | # source has ancestor - ancestor-new - change-rebased |
|
1281 | 1284 | backend.pull_heads(target, heads=['ancestor-new']) |
|
1282 | 1285 | backend.pull_heads(source, heads=['change-rebased']) |
|
1286 | target_repo_name = target.repo_name | |
|
1283 | 1287 | |
|
1284 | 1288 | # update PR |
|
1285 | 1289 | self.app.post( |
|
1286 | 1290 | route_path('pullrequest_update', |
|
1287 |
repo_name=target |
|
|
1291 | repo_name=target_repo_name, pull_request_id=pull_request_id), | |
|
1288 | 1292 | params={'update_commits': 'true', 'csrf_token': csrf_token}, |
|
1289 | 1293 | status=200) |
|
1290 | 1294 | |
@@ -1389,6 +1393,8 b' class TestPullrequestsView(object):' | |||
|
1389 | 1393 | pull_request = pr_util.create_pull_request( |
|
1390 | 1394 | commits, target_head='old-feature', source_head='new-feature', |
|
1391 | 1395 | revisions=['new-feature'], mergeable=True) |
|
1396 | pr_id = pull_request.pull_request_id | |
|
1397 | target_repo_name = pull_request.target_repo.repo_name | |
|
1392 | 1398 | |
|
1393 | 1399 | vcs = pr_util.source_repository.scm_instance() |
|
1394 | 1400 | if backend.alias == 'git': |
@@ -1397,8 +1403,8 b' class TestPullrequestsView(object):' | |||
|
1397 | 1403 | vcs.strip(pr_util.commit_ids['new-feature']) |
|
1398 | 1404 | |
|
1399 | 1405 | url = route_path('pullrequest_update', |
|
1400 |
repo_name= |
|
|
1401 |
pull_request_id=p |
|
|
1406 | repo_name=target_repo_name, | |
|
1407 | pull_request_id=pr_id) | |
|
1402 | 1408 | response = self.app.post(url, |
|
1403 | 1409 | params={'update_commits': 'true', |
|
1404 | 1410 | 'csrf_token': csrf_token}) |
@@ -1409,8 +1415,8 b' class TestPullrequestsView(object):' | |||
|
1409 | 1415 | # Make sure that after update, it won't raise 500 errors |
|
1410 | 1416 | response = self.app.get(route_path( |
|
1411 | 1417 | 'pullrequest_show', |
|
1412 |
repo_name= |
|
|
1413 |
pull_request_id=p |
|
|
1418 | repo_name=target_repo_name, | |
|
1419 | pull_request_id=pr_id)) | |
|
1414 | 1420 | |
|
1415 | 1421 | assert response.status_int == 200 |
|
1416 | 1422 | response.assert_response().element_contains( |
@@ -18,8 +18,8 b'' | |||
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | ||
|
22 | 21 | import logging |
|
22 | import collections | |
|
23 | 23 | |
|
24 | 24 | from pyramid.httpexceptions import ( |
|
25 | 25 | HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict) |
@@ -34,14 +34,14 b' from rhodecode.apps.file_store.exception' | |||
|
34 | 34 | from rhodecode.lib import diffs, codeblocks |
|
35 | 35 | from rhodecode.lib.auth import ( |
|
36 | 36 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) |
|
37 | ||
|
37 | from rhodecode.lib.ext_json import json | |
|
38 | 38 | from rhodecode.lib.compat import OrderedDict |
|
39 | 39 | from rhodecode.lib.diffs import ( |
|
40 | 40 | cache_diff, load_cached_diff, diff_cache_exist, get_diff_context, |
|
41 | 41 | get_diff_whitespace_flag) |
|
42 | 42 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch |
|
43 | 43 | import rhodecode.lib.helpers as h |
|
44 | from rhodecode.lib.utils2 import safe_unicode, str2bool | |
|
44 | from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict | |
|
45 | 45 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
46 | 46 | from rhodecode.lib.vcs.exceptions import ( |
|
47 | 47 | RepositoryError, CommitDoesNotExistError) |
@@ -115,6 +115,7 b' class RepoCommitsView(RepoAppView):' | |||
|
115 | 115 | except Exception: |
|
116 | 116 | log.exception("General failure") |
|
117 | 117 | raise HTTPNotFound() |
|
118 | single_commit = len(c.commit_ranges) == 1 | |
|
118 | 119 | |
|
119 | 120 | c.changes = OrderedDict() |
|
120 | 121 | c.lines_added = 0 |
@@ -128,23 +129,48 b' class RepoCommitsView(RepoAppView):' | |||
|
128 | 129 | c.inline_comments = [] |
|
129 | 130 | c.files = [] |
|
130 | 131 | |
|
131 | c.statuses = [] | |
|
132 | 132 | c.comments = [] |
|
133 | 133 | c.unresolved_comments = [] |
|
134 | 134 | c.resolved_comments = [] |
|
135 | if len(c.commit_ranges) == 1: | |
|
135 | ||
|
136 | # Single commit | |
|
137 | if single_commit: | |
|
136 | 138 | commit = c.commit_ranges[0] |
|
137 | 139 | c.comments = CommentsModel().get_comments( |
|
138 | 140 | self.db_repo.repo_id, |
|
139 | 141 | revision=commit.raw_id) |
|
140 | c.statuses.append(ChangesetStatusModel().get_status( | |
|
141 | self.db_repo.repo_id, commit.raw_id)) | |
|
142 | ||
|
142 | 143 | # comments from PR |
|
143 | 144 | statuses = ChangesetStatusModel().get_statuses( |
|
144 | 145 | self.db_repo.repo_id, commit.raw_id, |
|
145 | 146 | with_revisions=True) |
|
146 | prs = set(st.pull_request for st in statuses | |
|
147 | if st.pull_request is not None) | |
|
147 | ||
|
148 | prs = set() | |
|
149 | reviewers = list() | |
|
150 | reviewers_duplicates = set() # to not have duplicates from multiple votes | |
|
151 | for c_status in statuses: | |
|
152 | ||
|
153 | # extract associated pull-requests from votes | |
|
154 | if c_status.pull_request: | |
|
155 | prs.add(c_status.pull_request) | |
|
156 | ||
|
157 | # extract reviewers | |
|
158 | _user_id = c_status.author.user_id | |
|
159 | if _user_id not in reviewers_duplicates: | |
|
160 | reviewers.append( | |
|
161 | StrictAttributeDict({ | |
|
162 | 'user': c_status.author, | |
|
163 | ||
|
164 | # fake attributed for commit, page that we don't have | |
|
165 | # but we share the display with PR page | |
|
166 | 'mandatory': False, | |
|
167 | 'reasons': [], | |
|
168 | 'rule_user_group_data': lambda: None | |
|
169 | }) | |
|
170 | ) | |
|
171 | reviewers_duplicates.add(_user_id) | |
|
172 | ||
|
173 | c.allowed_reviewers = reviewers | |
|
148 | 174 | # from associated statuses, check the pull requests, and |
|
149 | 175 | # show comments from them |
|
150 | 176 | for pr in prs: |
@@ -155,6 +181,37 b' class RepoCommitsView(RepoAppView):' | |||
|
155 | 181 | c.resolved_comments = CommentsModel()\ |
|
156 | 182 | .get_commit_resolved_todos(commit.raw_id) |
|
157 | 183 | |
|
184 | c.inline_comments_flat = CommentsModel()\ | |
|
185 | .get_commit_inline_comments(commit.raw_id) | |
|
186 | ||
|
187 | review_statuses = ChangesetStatusModel().aggregate_votes_by_user( | |
|
188 | statuses, reviewers) | |
|
189 | ||
|
190 | c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED | |
|
191 | ||
|
192 | c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) | |
|
193 | ||
|
194 | for review_obj, member, reasons, mandatory, status in review_statuses: | |
|
195 | member_reviewer = h.reviewer_as_json( | |
|
196 | member, reasons=reasons, mandatory=mandatory, | |
|
197 | user_group=None | |
|
198 | ) | |
|
199 | ||
|
200 | current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED | |
|
201 | member_reviewer['review_status'] = current_review_status | |
|
202 | member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status) | |
|
203 | member_reviewer['allowed_to_update'] = False | |
|
204 | c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer) | |
|
205 | ||
|
206 | c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json) | |
|
207 | ||
|
208 | # NOTE(marcink): this uses the same voting logic as in pull-requests | |
|
209 | c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses) | |
|
210 | c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format( | |
|
211 | c.repo_name, | |
|
212 | commit.raw_id | |
|
213 | ) | |
|
214 | ||
|
158 | 215 | diff = None |
|
159 | 216 | # Iterate over ranges (default commit view is always one commit) |
|
160 | 217 | for commit in c.commit_ranges: |
@@ -166,8 +223,8 b' class RepoCommitsView(RepoAppView):' | |||
|
166 | 223 | if method == 'show': |
|
167 | 224 | inline_comments = CommentsModel().get_inline_comments( |
|
168 | 225 | self.db_repo.repo_id, revision=commit.raw_id) |
|
169 |
c.inline_cnt = CommentsModel().get_inline_comments_ |
|
|
170 | inline_comments) | |
|
226 | c.inline_cnt = len(CommentsModel().get_inline_comments_as_list( | |
|
227 | inline_comments)) | |
|
171 | 228 | c.inline_comments = inline_comments |
|
172 | 229 | |
|
173 | 230 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path( |
@@ -226,6 +283,7 b' class RepoCommitsView(RepoAppView):' | |||
|
226 | 283 | |
|
227 | 284 | # sort comments by how they were generated |
|
228 | 285 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) |
|
286 | c.at_version_num = None | |
|
229 | 287 | |
|
230 | 288 | if len(c.commit_ranges) == 1: |
|
231 | 289 | c.commit = c.commit_ranges[0] |
@@ -395,6 +453,7 b' class RepoCommitsView(RepoAppView):' | |||
|
395 | 453 | } |
|
396 | 454 | if comment: |
|
397 | 455 | c.co = comment |
|
456 | c.at_version_num = 0 | |
|
398 | 457 | rendered_comment = render( |
|
399 | 458 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
400 | 459 | self._get_template_context(c), self.request) |
@@ -427,7 +486,6 b' class RepoCommitsView(RepoAppView):' | |||
|
427 | 486 | return '' |
|
428 | 487 | |
|
429 | 488 | @LoginRequired() |
|
430 | @NotAnonymous() | |
|
431 | 489 | @HasRepoPermissionAnyDecorator( |
|
432 | 490 | 'repository.read', 'repository.write', 'repository.admin') |
|
433 | 491 | @CSRFRequired() |
@@ -39,7 +39,7 b' from rhodecode.lib.ext_json import json' | |||
|
39 | 39 | from rhodecode.lib.auth import ( |
|
40 | 40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, |
|
41 | 41 | NotAnonymous, CSRFRequired) |
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode | |
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int | |
|
43 | 43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
44 | 44 | from rhodecode.lib.vcs.exceptions import ( |
|
45 | 45 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) |
@@ -265,6 +265,36 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
265 | 265 | |
|
266 | 266 | return diffset |
|
267 | 267 | |
|
268 | def register_comments_vars(self, c, pull_request, versions): | |
|
269 | comments_model = CommentsModel() | |
|
270 | ||
|
271 | # GENERAL COMMENTS with versions # | |
|
272 | q = comments_model._all_general_comments_of_pull_request(pull_request) | |
|
273 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
274 | general_comments = q | |
|
275 | ||
|
276 | # pick comments we want to render at current version | |
|
277 | c.comment_versions = comments_model.aggregate_comments( | |
|
278 | general_comments, versions, c.at_version_num) | |
|
279 | ||
|
280 | # INLINE COMMENTS with versions # | |
|
281 | q = comments_model._all_inline_comments_of_pull_request(pull_request) | |
|
282 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
283 | inline_comments = q | |
|
284 | ||
|
285 | c.inline_versions = comments_model.aggregate_comments( | |
|
286 | inline_comments, versions, c.at_version_num, inline=True) | |
|
287 | ||
|
288 | # Comments inline+general | |
|
289 | if c.at_version: | |
|
290 | c.inline_comments_flat = c.inline_versions[c.at_version_num]['display'] | |
|
291 | c.comments = c.comment_versions[c.at_version_num]['display'] | |
|
292 | else: | |
|
293 | c.inline_comments_flat = c.inline_versions[c.at_version_num]['until'] | |
|
294 | c.comments = c.comment_versions[c.at_version_num]['until'] | |
|
295 | ||
|
296 | return general_comments, inline_comments | |
|
297 | ||
|
268 | 298 | @LoginRequired() |
|
269 | 299 | @HasRepoPermissionAnyDecorator( |
|
270 | 300 | 'repository.read', 'repository.write', 'repository.admin') |
@@ -280,6 +310,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
280 | 310 | pull_request_id = pull_request.pull_request_id |
|
281 | 311 | |
|
282 | 312 | c.state_progressing = pull_request.is_state_changing() |
|
313 | c.pr_broadcast_channel = '/repo${}$/pr/{}'.format( | |
|
314 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |
|
283 | 315 | |
|
284 | 316 | _new_state = { |
|
285 | 317 | 'created': PullRequest.STATE_CREATED, |
@@ -300,22 +332,23 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
300 | 332 | from_version = self.request.GET.get('from_version') or version |
|
301 | 333 | merge_checks = self.request.GET.get('merge_checks') |
|
302 | 334 | c.fulldiff = str2bool(self.request.GET.get('fulldiff')) |
|
335 | force_refresh = str2bool(self.request.GET.get('force_refresh')) | |
|
336 | c.range_diff_on = self.request.GET.get('range-diff') == "1" | |
|
303 | 337 | |
|
304 | 338 | # fetch global flags of ignore ws or context lines |
|
305 | 339 | diff_context = diffs.get_diff_context(self.request) |
|
306 | 340 | hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request) |
|
307 | 341 | |
|
308 | force_refresh = str2bool(self.request.GET.get('force_refresh')) | |
|
309 | ||
|
310 | 342 | (pull_request_latest, |
|
311 | 343 | pull_request_at_ver, |
|
312 | 344 | pull_request_display_obj, |
|
313 | 345 | at_version) = PullRequestModel().get_pr_version( |
|
314 | 346 | pull_request_id, version=version) |
|
347 | ||
|
315 | 348 | pr_closed = pull_request_latest.is_closed() |
|
316 | 349 | |
|
317 | 350 | if pr_closed and (version or from_version): |
|
318 | # not allow to browse versions | |
|
351 | # not allow to browse versions for closed PR | |
|
319 | 352 | raise HTTPFound(h.route_path( |
|
320 | 353 | 'pullrequest_show', repo_name=self.db_repo_name, |
|
321 | 354 | pull_request_id=pull_request_id)) |
@@ -323,13 +356,13 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
323 | 356 | versions = pull_request_display_obj.versions() |
|
324 | 357 | # used to store per-commit range diffs |
|
325 | 358 | c.changes = collections.OrderedDict() |
|
326 | c.range_diff_on = self.request.GET.get('range-diff') == "1" | |
|
327 | 359 | |
|
328 | 360 | c.at_version = at_version |
|
329 | 361 | c.at_version_num = (at_version |
|
330 |
if at_version and at_version != |
|
|
362 | if at_version and at_version != PullRequest.LATEST_VER | |
|
331 | 363 | else None) |
|
332 | c.at_version_pos = ChangesetComment.get_index_from_version( | |
|
364 | ||
|
365 | c.at_version_index = ChangesetComment.get_index_from_version( | |
|
333 | 366 | c.at_version_num, versions) |
|
334 | 367 | |
|
335 | 368 | (prev_pull_request_latest, |
@@ -340,9 +373,9 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
340 | 373 | |
|
341 | 374 | c.from_version = prev_at_version |
|
342 | 375 | c.from_version_num = (prev_at_version |
|
343 |
if prev_at_version and prev_at_version != |
|
|
376 | if prev_at_version and prev_at_version != PullRequest.LATEST_VER | |
|
344 | 377 | else None) |
|
345 |
c.from_version_ |
|
|
378 | c.from_version_index = ChangesetComment.get_index_from_version( | |
|
346 | 379 | c.from_version_num, versions) |
|
347 | 380 | |
|
348 | 381 | # define if we're in COMPARE mode or VIEW at version mode |
@@ -351,16 +384,21 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
351 | 384 | # pull_requests repo_name we opened it against |
|
352 | 385 | # ie. target_repo must match |
|
353 | 386 | if self.db_repo_name != pull_request_at_ver.target_repo.repo_name: |
|
387 | log.warning('Mismatch between the current repo: %s, and target %s', | |
|
388 | self.db_repo_name, pull_request_at_ver.target_repo.repo_name) | |
|
354 | 389 | raise HTTPNotFound() |
|
355 | 390 | |
|
356 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url( | |
|
357 | pull_request_at_ver) | |
|
391 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver) | |
|
358 | 392 | |
|
359 | 393 | c.pull_request = pull_request_display_obj |
|
360 | 394 | c.renderer = pull_request_at_ver.description_renderer or c.renderer |
|
361 | 395 | c.pull_request_latest = pull_request_latest |
|
362 | 396 | |
|
363 | if compare or (at_version and not at_version == 'latest'): | |
|
397 | # inject latest version | |
|
398 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |
|
399 | c.versions = versions + [latest_ver] | |
|
400 | ||
|
401 | if compare or (at_version and not at_version == PullRequest.LATEST_VER): | |
|
364 | 402 | c.allowed_to_change_status = False |
|
365 | 403 | c.allowed_to_update = False |
|
366 | 404 | c.allowed_to_merge = False |
@@ -389,12 +427,9 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
389 | 427 | 'rules' in pull_request_latest.reviewer_data: |
|
390 | 428 | rules = pull_request_latest.reviewer_data['rules'] or {} |
|
391 | 429 | try: |
|
392 | c.forbid_adding_reviewers = rules.get( | |
|
393 | 'forbid_adding_reviewers') | |
|
394 | c.forbid_author_to_review = rules.get( | |
|
395 | 'forbid_author_to_review') | |
|
396 | c.forbid_commit_author_to_review = rules.get( | |
|
397 | 'forbid_commit_author_to_review') | |
|
430 | c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers') | |
|
431 | c.forbid_author_to_review = rules.get('forbid_author_to_review') | |
|
432 | c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review') | |
|
398 | 433 | except Exception: |
|
399 | 434 | pass |
|
400 | 435 | |
@@ -419,41 +454,34 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
419 | 454 | 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako' |
|
420 | 455 | return self._get_template_context(c) |
|
421 | 456 | |
|
422 | comments_model = CommentsModel() | |
|
457 | c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user] | |
|
423 | 458 | |
|
424 | 459 | # reviewers and statuses |
|
425 |
c.pull_request_reviewers = |
|
|
426 | allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers] | |
|
460 | c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data) | |
|
461 | c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) | |
|
427 | 462 | |
|
428 | # GENERAL COMMENTS with versions # | |
|
429 | q = comments_model._all_general_comments_of_pull_request(pull_request_latest) | |
|
430 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
431 | general_comments = q | |
|
463 | for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses(): | |
|
464 | member_reviewer = h.reviewer_as_json( | |
|
465 | member, reasons=reasons, mandatory=mandatory, | |
|
466 | user_group=review_obj.rule_user_group_data() | |
|
467 | ) | |
|
432 | 468 | |
|
433 | # pick comments we want to render at current version | |
|
434 | c.comment_versions = comments_model.aggregate_comments( | |
|
435 | general_comments, versions, c.at_version_num) | |
|
436 | c.comments = c.comment_versions[c.at_version_num]['until'] | |
|
469 | current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED | |
|
470 | member_reviewer['review_status'] = current_review_status | |
|
471 | member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status) | |
|
472 | member_reviewer['allowed_to_update'] = c.allowed_to_update | |
|
473 | c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer) | |
|
437 | 474 | |
|
438 | # INLINE COMMENTS with versions # | |
|
439 | q = comments_model._all_inline_comments_of_pull_request(pull_request_latest) | |
|
440 | q = q.order_by(ChangesetComment.comment_id.asc()) | |
|
441 | inline_comments = q | |
|
475 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) | |
|
442 | 476 | |
|
443 | c.inline_versions = comments_model.aggregate_comments( | |
|
444 | inline_comments, versions, c.at_version_num, inline=True) | |
|
477 | general_comments, inline_comments = \ | |
|
478 | self.register_comments_vars(c, pull_request_latest, versions) | |
|
445 | 479 | |
|
446 | 480 | # TODOs |
|
447 | 481 | c.unresolved_comments = CommentsModel() \ |
|
448 | .get_pull_request_unresolved_todos(pull_request) | |
|
482 | .get_pull_request_unresolved_todos(pull_request_latest) | |
|
449 | 483 | c.resolved_comments = CommentsModel() \ |
|
450 | .get_pull_request_resolved_todos(pull_request) | |
|
451 | ||
|
452 | # inject latest version | |
|
453 | latest_ver = PullRequest.get_pr_display_object( | |
|
454 | pull_request_latest, pull_request_latest) | |
|
455 | ||
|
456 | c.versions = versions + [latest_ver] | |
|
484 | .get_pull_request_resolved_todos(pull_request_latest) | |
|
457 | 485 | |
|
458 | 486 | # if we use version, then do not show later comments |
|
459 | 487 | # than current version |
@@ -520,8 +548,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
520 | 548 | |
|
521 | 549 | # empty version means latest, so we keep this to prevent |
|
522 | 550 | # double caching |
|
523 |
version_normalized = version or |
|
|
524 |
from_version_normalized = from_version or |
|
|
551 | version_normalized = version or PullRequest.LATEST_VER | |
|
552 | from_version_normalized = from_version or PullRequest.LATEST_VER | |
|
525 | 553 | |
|
526 | 554 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo) |
|
527 | 555 | cache_file_path = diff_cache_exist( |
@@ -717,7 +745,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
717 | 745 | |
|
718 | 746 | # current user review statuses for each version |
|
719 | 747 | c.review_versions = {} |
|
720 | if self._rhodecode_user.user_id in allowed_reviewers: | |
|
748 | if self._rhodecode_user.user_id in c.allowed_reviewers: | |
|
721 | 749 | for co in general_comments: |
|
722 | 750 | if co.author.user_id == self._rhodecode_user.user_id: |
|
723 | 751 | status = co.status_change |
@@ -937,6 +965,90 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
937 | 965 | @NotAnonymous() |
|
938 | 966 | @HasRepoPermissionAnyDecorator( |
|
939 | 967 | 'repository.read', 'repository.write', 'repository.admin') |
|
968 | @view_config( | |
|
969 | route_name='pullrequest_comments', request_method='POST', | |
|
970 | renderer='string', xhr=True) | |
|
971 | def pullrequest_comments(self): | |
|
972 | self.load_default_context() | |
|
973 | ||
|
974 | pull_request = PullRequest.get_or_404( | |
|
975 | self.request.matchdict['pull_request_id']) | |
|
976 | pull_request_id = pull_request.pull_request_id | |
|
977 | version = self.request.GET.get('version') | |
|
978 | ||
|
979 | _render = self.request.get_partial_renderer( | |
|
980 | 'rhodecode:templates/base/sidebar.mako') | |
|
981 | c = _render.get_call_context() | |
|
982 | ||
|
983 | (pull_request_latest, | |
|
984 | pull_request_at_ver, | |
|
985 | pull_request_display_obj, | |
|
986 | at_version) = PullRequestModel().get_pr_version( | |
|
987 | pull_request_id, version=version) | |
|
988 | versions = pull_request_display_obj.versions() | |
|
989 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |
|
990 | c.versions = versions + [latest_ver] | |
|
991 | ||
|
992 | c.at_version = at_version | |
|
993 | c.at_version_num = (at_version | |
|
994 | if at_version and at_version != PullRequest.LATEST_VER | |
|
995 | else None) | |
|
996 | ||
|
997 | self.register_comments_vars(c, pull_request_latest, versions) | |
|
998 | all_comments = c.inline_comments_flat + c.comments | |
|
999 | ||
|
1000 | existing_ids = filter( | |
|
1001 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |
|
1002 | return _render('comments_table', all_comments, len(all_comments), | |
|
1003 | existing_ids=existing_ids) | |
|
1004 | ||
|
1005 | @LoginRequired() | |
|
1006 | @NotAnonymous() | |
|
1007 | @HasRepoPermissionAnyDecorator( | |
|
1008 | 'repository.read', 'repository.write', 'repository.admin') | |
|
1009 | @view_config( | |
|
1010 | route_name='pullrequest_todos', request_method='POST', | |
|
1011 | renderer='string', xhr=True) | |
|
1012 | def pullrequest_todos(self): | |
|
1013 | self.load_default_context() | |
|
1014 | ||
|
1015 | pull_request = PullRequest.get_or_404( | |
|
1016 | self.request.matchdict['pull_request_id']) | |
|
1017 | pull_request_id = pull_request.pull_request_id | |
|
1018 | version = self.request.GET.get('version') | |
|
1019 | ||
|
1020 | _render = self.request.get_partial_renderer( | |
|
1021 | 'rhodecode:templates/base/sidebar.mako') | |
|
1022 | c = _render.get_call_context() | |
|
1023 | (pull_request_latest, | |
|
1024 | pull_request_at_ver, | |
|
1025 | pull_request_display_obj, | |
|
1026 | at_version) = PullRequestModel().get_pr_version( | |
|
1027 | pull_request_id, version=version) | |
|
1028 | versions = pull_request_display_obj.versions() | |
|
1029 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |
|
1030 | c.versions = versions + [latest_ver] | |
|
1031 | ||
|
1032 | c.at_version = at_version | |
|
1033 | c.at_version_num = (at_version | |
|
1034 | if at_version and at_version != PullRequest.LATEST_VER | |
|
1035 | else None) | |
|
1036 | ||
|
1037 | c.unresolved_comments = CommentsModel() \ | |
|
1038 | .get_pull_request_unresolved_todos(pull_request) | |
|
1039 | c.resolved_comments = CommentsModel() \ | |
|
1040 | .get_pull_request_resolved_todos(pull_request) | |
|
1041 | ||
|
1042 | all_comments = c.unresolved_comments + c.resolved_comments | |
|
1043 | existing_ids = filter( | |
|
1044 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |
|
1045 | return _render('comments_table', all_comments, len(c.unresolved_comments), | |
|
1046 | todo_comments=True, existing_ids=existing_ids) | |
|
1047 | ||
|
1048 | @LoginRequired() | |
|
1049 | @NotAnonymous() | |
|
1050 | @HasRepoPermissionAnyDecorator( | |
|
1051 | 'repository.read', 'repository.write', 'repository.admin') | |
|
940 | 1052 | @CSRFRequired() |
|
941 | 1053 | @view_config( |
|
942 | 1054 | route_name='pullrequest_create', request_method='POST', |
@@ -1098,7 +1210,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1098 | 1210 | self.request.matchdict['pull_request_id']) |
|
1099 | 1211 | _ = self.request.translate |
|
1100 | 1212 | |
|
1101 | self.load_default_context() | |
|
1213 | c = self.load_default_context() | |
|
1102 | 1214 | redirect_url = None |
|
1103 | 1215 | |
|
1104 | 1216 | if pull_request.is_closed(): |
@@ -1109,6 +1221,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1109 | 1221 | 'redirect_url': redirect_url} |
|
1110 | 1222 | |
|
1111 | 1223 | is_state_changing = pull_request.is_state_changing() |
|
1224 | c.pr_broadcast_channel = '/repo${}$/pr/{}'.format( | |
|
1225 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |
|
1112 | 1226 | |
|
1113 | 1227 | # only owner or admin can update it |
|
1114 | 1228 | allowed_to_update = PullRequestModel().check_user_update( |
@@ -1132,7 +1246,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1132 | 1246 | return {'response': True, |
|
1133 | 1247 | 'redirect_url': redirect_url} |
|
1134 | 1248 | |
|
1135 | self._update_commits(pull_request) | |
|
1249 | self._update_commits(c, pull_request) | |
|
1136 | 1250 | if force_refresh: |
|
1137 | 1251 | redirect_url = h.route_path( |
|
1138 | 1252 | 'pullrequest_show', repo_name=self.db_repo_name, |
@@ -1168,7 +1282,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1168 | 1282 | h.flash(msg, category='success') |
|
1169 | 1283 | return |
|
1170 | 1284 | |
|
1171 | def _update_commits(self, pull_request): | |
|
1285 | def _update_commits(self, c, pull_request): | |
|
1172 | 1286 | _ = self.request.translate |
|
1173 | 1287 | |
|
1174 | 1288 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
@@ -1196,13 +1310,18 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1196 | 1310 | change_source=changed) |
|
1197 | 1311 | h.flash(msg, category='success') |
|
1198 | 1312 | |
|
1199 | channel = '/repo${}$/pr/{}'.format( | |
|
1200 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |
|
1201 | 1313 | message = msg + ( |
|
1202 | 1314 | ' - <a onclick="window.location.reload()">' |
|
1203 | 1315 | '<strong>{}</strong></a>'.format(_('Reload page'))) |
|
1316 | ||
|
1317 | message_obj = { | |
|
1318 | 'message': message, | |
|
1319 | 'level': 'success', | |
|
1320 | 'topic': '/notifications' | |
|
1321 | } | |
|
1322 | ||
|
1204 | 1323 | channelstream.post_message( |
|
1205 | channel, message, self._rhodecode_user.username, | |
|
1324 | c.pr_broadcast_channel, message_obj, self._rhodecode_user.username, | |
|
1206 | 1325 | registry=self.request.registry) |
|
1207 | 1326 | else: |
|
1208 | 1327 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] |
@@ -1472,6 +1591,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1472 | 1591 | } |
|
1473 | 1592 | if comment: |
|
1474 | 1593 | c.co = comment |
|
1594 | c.at_version_num = None | |
|
1475 | 1595 | rendered_comment = render( |
|
1476 | 1596 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
1477 | 1597 | self._get_template_context(c), self.request) |
@@ -1890,7 +1890,7 b'' | |||
|
1890 | 1890 | "url": "http://spdx.org/licenses/BSD-4-Clause.html" |
|
1891 | 1891 | } |
|
1892 | 1892 | ], |
|
1893 |
"name": "python2.7-channelstream-0. |
|
|
1893 | "name": "python2.7-channelstream-0.6.14" | |
|
1894 | 1894 | }, |
|
1895 | 1895 | { |
|
1896 | 1896 | "license": [ |
@@ -53,7 +53,7 b' from rhodecode.lib.utils2 import aslist ' | |||
|
53 | 53 | from rhodecode.lib.exc_tracking import store_exception |
|
54 | 54 | from rhodecode.subscribers import ( |
|
55 | 55 | scan_repositories_if_enabled, write_js_routes_if_enabled, |
|
56 | write_metadata_if_needed, inject_app_settings) | |
|
56 | write_metadata_if_needed, write_usage_data, inject_app_settings) | |
|
57 | 57 | |
|
58 | 58 | |
|
59 | 59 | log = logging.getLogger(__name__) |
@@ -316,6 +316,8 b' def includeme(config):' | |||
|
316 | 316 | pyramid.events.ApplicationCreated) |
|
317 | 317 | config.add_subscriber(write_metadata_if_needed, |
|
318 | 318 | pyramid.events.ApplicationCreated) |
|
319 | config.add_subscriber(write_usage_data, | |
|
320 | pyramid.events.ApplicationCreated) | |
|
319 | 321 | config.add_subscriber(write_js_routes_if_enabled, |
|
320 | 322 | pyramid.events.ApplicationCreated) |
|
321 | 323 |
@@ -145,7 +145,7 b' class PullRequestCommentEvent(PullReques' | |||
|
145 | 145 | |
|
146 | 146 | status = None |
|
147 | 147 | if self.comment.status_change: |
|
148 |
status = self.comment. |
|
|
148 | status = self.comment.review_status | |
|
149 | 149 | |
|
150 | 150 | data.update({ |
|
151 | 151 | 'comment': { |
@@ -184,7 +184,7 b' class PullRequestCommentEditEvent(PullRe' | |||
|
184 | 184 | |
|
185 | 185 | status = None |
|
186 | 186 | if self.comment.status_change: |
|
187 |
status = self.comment. |
|
|
187 | status = self.comment.review_status | |
|
188 | 188 | |
|
189 | 189 | data.update({ |
|
190 | 190 | 'comment': { |
@@ -37,7 +37,8 b' log = logging.getLogger(__name__)' | |||
|
37 | 37 | |
|
38 | 38 | LOCK = ReadWriteMutex() |
|
39 | 39 | |
|
40 | STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name', | |
|
40 | USER_STATE_PUBLIC_KEYS = [ | |
|
41 | 'id', 'username', 'first_name', 'last_name', | |
|
41 | 42 |
|
|
42 | 43 | |
|
43 | 44 | |
@@ -64,6 +65,8 b' def channelstream_request(config, payloa' | |||
|
64 | 65 | 'x-channelstream-endpoint': endpoint, |
|
65 | 66 | 'Content-Type': 'application/json'} |
|
66 | 67 | req_url = get_channelstream_server_url(config, endpoint) |
|
68 | ||
|
69 | log.debug('Sending a channelstream request to endpoint: `%s`', req_url) | |
|
67 | 70 | response = None |
|
68 | 71 | try: |
|
69 | 72 | response = requests.post(req_url, data=json.dumps(payload), |
@@ -76,6 +79,7 b' def channelstream_request(config, payloa' | |||
|
76 | 79 | log.exception('Exception related to Channelstream happened') |
|
77 | 80 | if raise_exc: |
|
78 | 81 | raise ChannelstreamConnectionException() |
|
82 | log.debug('Got channelstream response: %s', response) | |
|
79 | 83 | return response |
|
80 | 84 | |
|
81 | 85 | |
@@ -154,7 +158,7 b' def parse_channels_info(info_result, inc' | |||
|
154 | 158 | for userinfo in info_result['users']: |
|
155 | 159 | user_state_dict[userinfo['user']] = { |
|
156 | 160 | k: v for k, v in userinfo['state'].items() |
|
157 | if k in STATE_PUBLIC_KEYS | |
|
161 | if k in USER_STATE_PUBLIC_KEYS | |
|
158 | 162 | } |
|
159 | 163 | |
|
160 | 164 | channels_info = {} |
@@ -163,10 +167,10 b' def parse_channels_info(info_result, inc' | |||
|
163 | 167 | if c_name not in include_channel_info: |
|
164 | 168 | continue |
|
165 | 169 | connected_list = [] |
|
166 |
for user |
|
|
170 | for username in c_info['users']: | |
|
167 | 171 | connected_list.append({ |
|
168 |
'user': user |
|
|
169 |
'state': user_state_dict[user |
|
|
172 | 'user': username, | |
|
173 | 'state': user_state_dict[username] | |
|
170 | 174 | }) |
|
171 | 175 | channels_info[c_name] = {'users': connected_list, |
|
172 | 176 | 'history': c_info['history']} |
@@ -230,6 +234,14 b' def get_connection_validators(registry):' | |||
|
230 | 234 | |
|
231 | 235 | def post_message(channel, message, username, registry=None): |
|
232 | 236 | |
|
237 | message_obj = message | |
|
238 | if isinstance(message, basestring): | |
|
239 | message_obj = { | |
|
240 | 'message': message, | |
|
241 | 'level': 'success', | |
|
242 | 'topic': '/notifications' | |
|
243 | } | |
|
244 | ||
|
233 | 245 | if not registry: |
|
234 | 246 | registry = get_current_registry() |
|
235 | 247 | |
@@ -243,11 +255,7 b' def post_message(channel, message, usern' | |||
|
243 | 255 | 'user': 'system', |
|
244 | 256 | 'exclude_users': [username], |
|
245 | 257 | 'channel': channel, |
|
246 |
'message': |
|
|
247 | 'message': message, | |
|
248 | 'level': 'success', | |
|
249 | 'topic': '/notifications' | |
|
250 | } | |
|
258 | 'message': message_obj | |
|
251 | 259 | } |
|
252 | 260 | |
|
253 | 261 | try: |
@@ -90,7 +90,7 b' from rhodecode.lib.vcs.conf.settings imp' | |||
|
90 | 90 | from rhodecode.lib.index.search_utils import get_matching_line_offsets |
|
91 | 91 | from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT |
|
92 | 92 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
93 | from rhodecode.model.db import Permission, User, Repository, UserApiKeys | |
|
93 | from rhodecode.model.db import Permission, User, Repository, UserApiKeys, FileStore | |
|
94 | 94 | from rhodecode.model.repo_group import RepoGroupModel |
|
95 | 95 | from rhodecode.model.settings import IssueTrackerSettingsModel |
|
96 | 96 | |
@@ -810,8 +810,7 b' import tzlocal' | |||
|
810 | 810 | local_timezone = tzlocal.get_localzone() |
|
811 | 811 | |
|
812 | 812 | |
|
813 |
def |
|
|
814 | title = value or format_date(datetime_iso) | |
|
813 | def get_timezone(datetime_iso, time_is_local=False): | |
|
815 | 814 | tzinfo = '+00:00' |
|
816 | 815 | |
|
817 | 816 | # detect if we have a timezone info, otherwise, add it |
@@ -822,6 +821,12 b' def age_component(datetime_iso, value=No' | |||
|
822 | 821 | timezone = force_timezone or local_timezone |
|
823 | 822 | offset = timezone.localize(datetime_iso).strftime('%z') |
|
824 | 823 | tzinfo = '{}:{}'.format(offset[:-2], offset[-2:]) |
|
824 | return tzinfo | |
|
825 | ||
|
826 | ||
|
827 | def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True): | |
|
828 | title = value or format_date(datetime_iso) | |
|
829 | tzinfo = get_timezone(datetime_iso, time_is_local=time_is_local) | |
|
825 | 830 | |
|
826 | 831 | return literal( |
|
827 | 832 | '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format( |
@@ -1357,20 +1362,76 b' class InitialsGravatar(object):' | |||
|
1357 | 1362 | return "data:image/svg+xml;base64,%s" % base64.b64encode(img_data) |
|
1358 | 1363 | |
|
1359 | 1364 | |
|
1360 | def initials_gravatar(email_address, first_name, last_name, size=30): | |
|
1365 | def initials_gravatar(request, email_address, first_name, last_name, size=30, store_on_disk=False): | |
|
1366 | ||
|
1361 | 1367 | svg_type = None |
|
1362 | 1368 | if email_address == User.DEFAULT_USER_EMAIL: |
|
1363 | 1369 | svg_type = 'default_user' |
|
1370 | ||
|
1364 | 1371 | klass = InitialsGravatar(email_address, first_name, last_name, size) |
|
1372 | ||
|
1373 | if store_on_disk: | |
|
1374 | from rhodecode.apps.file_store import utils as store_utils | |
|
1375 | from rhodecode.apps.file_store.exceptions import FileNotAllowedException, \ | |
|
1376 | FileOverSizeException | |
|
1377 | from rhodecode.model.db import Session | |
|
1378 | ||
|
1379 | image_key = md5_safe(email_address.lower() | |
|
1380 | + first_name.lower() + last_name.lower()) | |
|
1381 | ||
|
1382 | storage = store_utils.get_file_storage(request.registry.settings) | |
|
1383 | filename = '{}.svg'.format(image_key) | |
|
1384 | subdir = 'gravatars' | |
|
1385 | # since final name has a counter, we apply the 0 | |
|
1386 | uid = storage.apply_counter(0, store_utils.uid_filename(filename, randomized=False)) | |
|
1387 | store_uid = os.path.join(subdir, uid) | |
|
1388 | ||
|
1389 | db_entry = FileStore.get_by_store_uid(store_uid) | |
|
1390 | if db_entry: | |
|
1391 | return request.route_path('download_file', fid=store_uid) | |
|
1392 | ||
|
1393 | img_data = klass.get_img_data(svg_type=svg_type) | |
|
1394 | img_file = store_utils.bytes_to_file_obj(img_data) | |
|
1395 | ||
|
1396 | try: | |
|
1397 | store_uid, metadata = storage.save_file( | |
|
1398 | img_file, filename, directory=subdir, | |
|
1399 | extensions=['.svg'], randomized_name=False) | |
|
1400 | except (FileNotAllowedException, FileOverSizeException): | |
|
1401 | raise | |
|
1402 | ||
|
1403 | try: | |
|
1404 | entry = FileStore.create( | |
|
1405 | file_uid=store_uid, filename=metadata["filename"], | |
|
1406 | file_hash=metadata["sha256"], file_size=metadata["size"], | |
|
1407 | file_display_name=filename, | |
|
1408 | file_description=u'user gravatar `{}`'.format(safe_unicode(filename)), | |
|
1409 | hidden=True, check_acl=False, user_id=1 | |
|
1410 | ) | |
|
1411 | Session().add(entry) | |
|
1412 | Session().commit() | |
|
1413 | log.debug('Stored upload in DB as %s', entry) | |
|
1414 | except Exception: | |
|
1415 | raise | |
|
1416 | ||
|
1417 | return request.route_path('download_file', fid=store_uid) | |
|
1418 | ||
|
1419 | else: | |
|
1365 | 1420 | return klass.generate_svg(svg_type=svg_type) |
|
1366 | 1421 | |
|
1367 | 1422 | |
|
1423 | def gravatar_external(request, gravatar_url_tmpl, email_address, size=30): | |
|
1424 | return safe_str(gravatar_url_tmpl)\ | |
|
1425 | .replace('{email}', email_address) \ | |
|
1426 | .replace('{md5email}', md5_safe(email_address.lower())) \ | |
|
1427 | .replace('{netloc}', request.host) \ | |
|
1428 | .replace('{scheme}', request.scheme) \ | |
|
1429 | .replace('{size}', safe_str(size)) | |
|
1430 | ||
|
1431 | ||
|
1368 | 1432 | def gravatar_url(email_address, size=30, request=None): |
|
1369 | request = get_current_request() | |
|
1433 | request = request or get_current_request() | |
|
1370 | 1434 | _use_gravatar = request.call_context.visual.use_gravatar |
|
1371 | _gravatar_url = request.call_context.visual.gravatar_url | |
|
1372 | ||
|
1373 | _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL | |
|
1374 | 1435 | |
|
1375 | 1436 | email_address = email_address or User.DEFAULT_USER_EMAIL |
|
1376 | 1437 | if isinstance(email_address, unicode): |
@@ -1379,21 +1440,15 b' def gravatar_url(email_address, size=30,' | |||
|
1379 | 1440 | |
|
1380 | 1441 | # empty email or default user |
|
1381 | 1442 | if not email_address or email_address == User.DEFAULT_USER_EMAIL: |
|
1382 | return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size) | |
|
1443 | return initials_gravatar(request, User.DEFAULT_USER_EMAIL, '', '', size=size) | |
|
1383 | 1444 | |
|
1384 | 1445 | if _use_gravatar: |
|
1385 | # TODO: Disuse pyramid thread locals. Think about another solution to | |
|
1386 | # get the host and schema here. | |
|
1387 | request = get_current_request() | |
|
1388 | tmpl = safe_str(_gravatar_url) | |
|
1389 | tmpl = tmpl.replace('{email}', email_address)\ | |
|
1390 | .replace('{md5email}', md5_safe(email_address.lower())) \ | |
|
1391 | .replace('{netloc}', request.host)\ | |
|
1392 | .replace('{scheme}', request.scheme)\ | |
|
1393 | .replace('{size}', safe_str(size)) | |
|
1394 | return tmpl | |
|
1446 | gravatar_url_tmpl = request.call_context.visual.gravatar_url \ | |
|
1447 | or User.DEFAULT_GRAVATAR_URL | |
|
1448 | return gravatar_external(request, gravatar_url_tmpl, email_address, size=size) | |
|
1449 | ||
|
1395 | 1450 | else: |
|
1396 | return initials_gravatar(email_address, '', '', size=size) | |
|
1451 | return initials_gravatar(request, email_address, '', '', size=size) | |
|
1397 | 1452 | |
|
1398 | 1453 | |
|
1399 | 1454 | def breadcrumb_repo_link(repo): |
@@ -1560,7 +1615,7 b' def _process_url_func(match_obj, repo_na' | |||
|
1560 | 1615 | # named regex variables |
|
1561 | 1616 | named_vars.update(match_obj.groupdict()) |
|
1562 | 1617 | _url = string.Template(entry['url']).safe_substitute(**named_vars) |
|
1563 | desc = string.Template(entry['desc']).safe_substitute(**named_vars) | |
|
1618 | desc = string.Template(escape(entry['desc'])).safe_substitute(**named_vars) | |
|
1564 | 1619 | hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars) |
|
1565 | 1620 | |
|
1566 | 1621 | def quote_cleaner(input_str): |
@@ -1600,17 +1655,18 b' def get_active_pattern_entries(repo_name' | |||
|
1600 | 1655 | |
|
1601 | 1656 | pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)') |
|
1602 | 1657 | |
|
1658 | allowed_link_formats = [ | |
|
1659 | 'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] | |
|
1660 | ||
|
1603 | 1661 | |
|
1604 | 1662 | def process_patterns(text_string, repo_name, link_format='html', active_entries=None): |
|
1605 | 1663 | |
|
1606 | allowed_formats = ['html', 'rst', 'markdown', | |
|
1607 | 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] | |
|
1608 | if link_format not in allowed_formats: | |
|
1664 | if link_format not in allowed_link_formats: | |
|
1609 | 1665 | raise ValueError('Link format can be only one of:{} got {}'.format( |
|
1610 | allowed_formats, link_format)) | |
|
1666 | allowed_link_formats, link_format)) | |
|
1611 | 1667 | |
|
1612 | 1668 | if active_entries is None: |
|
1613 | log.debug('Fetch active patterns for repo: %s', repo_name) | |
|
1669 | log.debug('Fetch active issue tracker patterns for repo: %s', repo_name) | |
|
1614 | 1670 | active_entries = get_active_pattern_entries(repo_name) |
|
1615 | 1671 | |
|
1616 | 1672 | issues_data = [] |
@@ -1668,7 +1724,8 b' def process_patterns(text_string, repo_n' | |||
|
1668 | 1724 | return new_text, issues_data |
|
1669 | 1725 | |
|
1670 | 1726 | |
|
1671 |
def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None |
|
|
1727 | def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None, | |
|
1728 | issues_container=None): | |
|
1672 | 1729 | """ |
|
1673 | 1730 | Parses given text message and makes proper links. |
|
1674 | 1731 | issues are linked to given issue-server, and rest is a commit link |
@@ -1691,6 +1748,9 b' def urlify_commit_message(commit_text, r' | |||
|
1691 | 1748 | new_text, issues = process_patterns(new_text, repository or '', |
|
1692 | 1749 | active_entries=active_pattern_entries) |
|
1693 | 1750 | |
|
1751 | if issues_container is not None: | |
|
1752 | issues_container.extend(issues) | |
|
1753 | ||
|
1694 | 1754 | return literal(new_text) |
|
1695 | 1755 | |
|
1696 | 1756 | |
@@ -1731,7 +1791,7 b' def renderer_from_filename(filename, exc' | |||
|
1731 | 1791 | |
|
1732 | 1792 | |
|
1733 | 1793 | def render(source, renderer='rst', mentions=False, relative_urls=None, |
|
1734 | repo_name=None, active_pattern_entries=None): | |
|
1794 | repo_name=None, active_pattern_entries=None, issues_container=None): | |
|
1735 | 1795 | |
|
1736 | 1796 | def maybe_convert_relative_links(html_source): |
|
1737 | 1797 | if relative_urls: |
@@ -1748,6 +1808,8 b" def render(source, renderer='rst', menti" | |||
|
1748 | 1808 | source, issues = process_patterns( |
|
1749 | 1809 | source, repo_name, link_format='rst', |
|
1750 | 1810 | active_entries=active_pattern_entries) |
|
1811 | if issues_container is not None: | |
|
1812 | issues_container.extend(issues) | |
|
1751 | 1813 | |
|
1752 | 1814 | return literal( |
|
1753 | 1815 | '<div class="rst-block">%s</div>' % |
@@ -1760,6 +1822,8 b" def render(source, renderer='rst', menti" | |||
|
1760 | 1822 | source, issues = process_patterns( |
|
1761 | 1823 | source, repo_name, link_format='markdown', |
|
1762 | 1824 | active_entries=active_pattern_entries) |
|
1825 | if issues_container is not None: | |
|
1826 | issues_container.extend(issues) | |
|
1763 | 1827 | |
|
1764 | 1828 | return literal( |
|
1765 | 1829 | '<div class="markdown-block">%s</div>' % |
@@ -139,6 +139,18 b' def is_vcs_call(environ):' | |||
|
139 | 139 | return False |
|
140 | 140 | |
|
141 | 141 | |
|
142 | def get_path_elem(route_path): | |
|
143 | if not route_path: | |
|
144 | return None | |
|
145 | ||
|
146 | cleaned_route_path = route_path.lstrip('/') | |
|
147 | if cleaned_route_path: | |
|
148 | cleaned_route_path_elems = cleaned_route_path.split('/') | |
|
149 | if cleaned_route_path_elems: | |
|
150 | return cleaned_route_path_elems[0] | |
|
151 | return None | |
|
152 | ||
|
153 | ||
|
142 | 154 | def detect_vcs_request(environ, backends): |
|
143 | 155 | checks = { |
|
144 | 156 | 'hg': (is_hg, SimpleHg), |
@@ -146,6 +158,17 b' def detect_vcs_request(environ, backends' | |||
|
146 | 158 | 'svn': (is_svn, SimpleSvn), |
|
147 | 159 | } |
|
148 | 160 | handler = None |
|
161 | # List of path views first chunk we don't do any checks | |
|
162 | white_list = [ | |
|
163 | # e.g /_file_store/download | |
|
164 | '_file_store' | |
|
165 | ] | |
|
166 | ||
|
167 | path_info = environ['PATH_INFO'] | |
|
168 | ||
|
169 | if get_path_elem(path_info) in white_list: | |
|
170 | log.debug('path `%s` in whitelist, skipping...', path_info) | |
|
171 | return handler | |
|
149 | 172 | |
|
150 | 173 | if VCS_TYPE_KEY in environ: |
|
151 | 174 | raw_type = environ[VCS_TYPE_KEY] |
@@ -224,7 +224,10 b' class RedisAuthSessions(BaseAuthSessions' | |||
|
224 | 224 | data = client.get(key) |
|
225 | 225 | if data: |
|
226 | 226 | json_data = pickle.loads(data) |
|
227 | try: | |
|
227 | 228 | accessed_time = json_data['_accessed_time'] |
|
229 | except KeyError: | |
|
230 | accessed_time = 0 | |
|
228 | 231 | if accessed_time < expiry_time: |
|
229 | 232 | client.delete(key) |
|
230 | 233 | deleted_keys += 1 |
@@ -212,10 +212,10 b' class ChangesetStatusModel(BaseModel):' | |||
|
212 | 212 | # TODO(marcink): with group voting, how does rejected work, |
|
213 | 213 | # do we ever get rejected state ? |
|
214 | 214 | |
|
215 | if approved_votes_count == reviewers_number: | |
|
215 | if approved_votes_count and (approved_votes_count == reviewers_number): | |
|
216 | 216 | return ChangesetStatus.STATUS_APPROVED |
|
217 | 217 | |
|
218 | if rejected_votes_count == reviewers_number: | |
|
218 | if rejected_votes_count and (rejected_votes_count == reviewers_number): | |
|
219 | 219 | return ChangesetStatus.STATUS_REJECTED |
|
220 | 220 | |
|
221 | 221 | return ChangesetStatus.STATUS_UNDER_REVIEW |
@@ -354,34 +354,37 b' class ChangesetStatusModel(BaseModel):' | |||
|
354 | 354 | Session().add(new_status) |
|
355 | 355 | return new_statuses |
|
356 | 356 | |
|
357 | def aggregate_votes_by_user(self, commit_statuses, reviewers_data): | |
|
358 | ||
|
359 | commit_statuses_map = collections.defaultdict(list) | |
|
360 | for st in commit_statuses: | |
|
361 | commit_statuses_map[st.author.username] += [st] | |
|
362 | ||
|
363 | reviewers = [] | |
|
364 | ||
|
365 | def version(commit_status): | |
|
366 | return commit_status.version | |
|
367 | ||
|
368 | for obj in reviewers_data: | |
|
369 | if not obj.user: | |
|
370 | continue | |
|
371 | statuses = commit_statuses_map.get(obj.user.username, None) | |
|
372 | if statuses: | |
|
373 | status_groups = itertools.groupby( | |
|
374 | sorted(statuses, key=version), version) | |
|
375 | statuses = [(x, list(y)[0]) for x, y in status_groups] | |
|
376 | ||
|
377 | reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses)) | |
|
378 | ||
|
379 | return reviewers | |
|
380 | ||
|
357 | 381 | def reviewers_statuses(self, pull_request): |
|
358 | 382 | _commit_statuses = self.get_statuses( |
|
359 | 383 | pull_request.source_repo, |
|
360 | 384 | pull_request=pull_request, |
|
361 | 385 | with_revisions=True) |
|
362 | 386 | |
|
363 | commit_statuses = collections.defaultdict(list) | |
|
364 | for st in _commit_statuses: | |
|
365 | commit_statuses[st.author.username] += [st] | |
|
366 | ||
|
367 | pull_request_reviewers = [] | |
|
368 | ||
|
369 | def version(commit_status): | |
|
370 | return commit_status.version | |
|
371 | ||
|
372 | for obj in pull_request.reviewers: | |
|
373 | if not obj.user: | |
|
374 | continue | |
|
375 | statuses = commit_statuses.get(obj.user.username, None) | |
|
376 | if statuses: | |
|
377 | status_groups = itertools.groupby( | |
|
378 | sorted(statuses, key=version), version) | |
|
379 | statuses = [(x, list(y)[0]) for x, y in status_groups] | |
|
380 | ||
|
381 | pull_request_reviewers.append( | |
|
382 | (obj, obj.user, obj.reasons, obj.mandatory, statuses)) | |
|
383 | ||
|
384 | return pull_request_reviewers | |
|
387 | return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) | |
|
385 | 388 | |
|
386 | 389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): |
|
387 | 390 | """ |
@@ -91,8 +91,7 b' class CommentsModel(BaseModel):' | |||
|
91 | 91 | # group by versions, and count until, and display objects |
|
92 | 92 | |
|
93 | 93 | comment_groups = collections.defaultdict(list) |
|
94 | [comment_groups[ | |
|
95 | _co.pull_request_version_id].append(_co) for _co in comments] | |
|
94 | [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments] | |
|
96 | 95 | |
|
97 | 96 | def yield_comments(pos): |
|
98 | 97 | for co in comment_groups[pos]: |
@@ -229,6 +228,14 b' class CommentsModel(BaseModel):' | |||
|
229 | 228 | |
|
230 | 229 | return todos |
|
231 | 230 | |
|
231 | def get_commit_inline_comments(self, commit_id): | |
|
232 | inline_comments = Session().query(ChangesetComment) \ | |
|
233 | .filter(ChangesetComment.line_no != None) \ | |
|
234 | .filter(ChangesetComment.f_path != None) \ | |
|
235 | .filter(ChangesetComment.revision == commit_id) | |
|
236 | inline_comments = inline_comments.all() | |
|
237 | return inline_comments | |
|
238 | ||
|
232 | 239 | def _log_audit_action(self, action, action_data, auth_user, comment): |
|
233 | 240 | audit_logger.store( |
|
234 | 241 | action=action, |
@@ -456,37 +463,53 b' class CommentsModel(BaseModel):' | |||
|
456 | 463 | else: |
|
457 | 464 | action = 'repo.commit.comment.create' |
|
458 | 465 | |
|
466 | comment_id = comment.comment_id | |
|
459 | 467 | comment_data = comment.get_api_data() |
|
468 | ||
|
460 | 469 | self._log_audit_action( |
|
461 | 470 | action, {'data': comment_data}, auth_user, comment) |
|
462 | 471 | |
|
463 | msg_url = '' | |
|
464 | 472 | channel = None |
|
465 | 473 | if commit_obj: |
|
466 | msg_url = commit_comment_url | |
|
467 | 474 | repo_name = repo.repo_name |
|
468 | 475 | channel = u'/repo${}$/commit/{}'.format( |
|
469 | 476 | repo_name, |
|
470 | 477 | commit_obj.raw_id |
|
471 | 478 | ) |
|
472 | 479 | elif pull_request_obj: |
|
473 | msg_url = pr_comment_url | |
|
474 | 480 | repo_name = pr_target_repo.repo_name |
|
475 | 481 | channel = u'/repo${}$/pr/{}'.format( |
|
476 | 482 | repo_name, |
|
477 | pull_request_id | |
|
483 | pull_request_obj.pull_request_id | |
|
478 | 484 | ) |
|
479 | 485 | |
|
480 | message = '<strong>{}</strong> {} - ' \ | |
|
481 | '<a onclick="window.location=\'{}\';' \ | |
|
482 | 'window.location.reload()">' \ | |
|
483 | '<strong>{}</strong></a>' | |
|
486 | if channel: | |
|
487 | username = user.username | |
|
488 | message = '<strong>{}</strong> {} #{}, {}' | |
|
484 | 489 | message = message.format( |
|
485 | user.username, _('made a comment'), msg_url, | |
|
486 | _('Show it now')) | |
|
490 | username, | |
|
491 | _('posted a new comment'), | |
|
492 | comment_id, | |
|
493 | _('Refresh the page to see new comments.')) | |
|
494 | ||
|
495 | message_obj = { | |
|
496 | 'message': message, | |
|
497 | 'level': 'success', | |
|
498 | 'topic': '/notifications' | |
|
499 | } | |
|
487 | 500 | |
|
488 | 501 | channelstream.post_message( |
|
489 | channel, message, user.username, | |
|
502 | channel, message_obj, user.username, | |
|
503 | registry=get_current_registry()) | |
|
504 | ||
|
505 | message_obj = { | |
|
506 | 'message': None, | |
|
507 | 'user': username, | |
|
508 | 'comment_id': comment_id, | |
|
509 | 'topic': '/comment' | |
|
510 | } | |
|
511 | channelstream.post_message( | |
|
512 | channel, message_obj, user.username, | |
|
490 | 513 | registry=get_current_registry()) |
|
491 | 514 | |
|
492 | 515 | return comment |
@@ -641,16 +664,16 b' class CommentsModel(BaseModel):' | |||
|
641 | 664 | q = self._get_inline_comments_query(repo_id, revision, pull_request) |
|
642 | 665 | return self._group_comments_by_path_and_line_number(q) |
|
643 | 666 | |
|
644 |
def get_inline_comments_ |
|
|
667 | def get_inline_comments_as_list(self, inline_comments, skip_outdated=True, | |
|
645 | 668 | version=None): |
|
646 |
inline_c |
|
|
669 | inline_comms = [] | |
|
647 | 670 | for fname, per_line_comments in inline_comments.iteritems(): |
|
648 | 671 | for lno, comments in per_line_comments.iteritems(): |
|
649 | 672 | for comm in comments: |
|
650 | 673 | if not comm.outdated_at_version(version) and skip_outdated: |
|
651 |
inline_c |
|
|
674 | inline_comms.append(comm) | |
|
652 | 675 | |
|
653 |
return inline_c |
|
|
676 | return inline_comms | |
|
654 | 677 | |
|
655 | 678 | def get_outdated_comments(self, repo_id, pull_request): |
|
656 | 679 | # TODO: johbo: Remove `repo_id`, it is not needed to find the comments |
@@ -3810,6 +3810,10 b' class ChangesetComment(Base, BaseModel):' | |||
|
3810 | 3810 | return self.display_state == self.COMMENT_OUTDATED |
|
3811 | 3811 | |
|
3812 | 3812 | @property |
|
3813 | def outdated_js(self): | |
|
3814 | return json.dumps(self.display_state == self.COMMENT_OUTDATED) | |
|
3815 | ||
|
3816 | @property | |
|
3813 | 3817 | def immutable(self): |
|
3814 | 3818 | return self.immutable_state == self.OP_IMMUTABLE |
|
3815 | 3819 | |
@@ -3817,16 +3821,35 b' class ChangesetComment(Base, BaseModel):' | |||
|
3817 | 3821 | """ |
|
3818 | 3822 | Checks if comment is outdated for given pull request version |
|
3819 | 3823 | """ |
|
3820 | return self.outdated and self.pull_request_version_id != version | |
|
3824 | def version_check(): | |
|
3825 | return self.pull_request_version_id and self.pull_request_version_id != version | |
|
3826 | ||
|
3827 | if self.is_inline: | |
|
3828 | return self.outdated and version_check() | |
|
3829 | else: | |
|
3830 | # general comments don't have .outdated set, also latest don't have a version | |
|
3831 | return version_check() | |
|
3832 | ||
|
3833 | def outdated_at_version_js(self, version): | |
|
3834 | """ | |
|
3835 | Checks if comment is outdated for given pull request version | |
|
3836 | """ | |
|
3837 | return json.dumps(self.outdated_at_version(version)) | |
|
3821 | 3838 | |
|
3822 | 3839 | def older_than_version(self, version): |
|
3823 | 3840 | """ |
|
3824 | 3841 | Checks if comment is made from previous version than given |
|
3825 | 3842 | """ |
|
3826 | 3843 | if version is None: |
|
3827 |
return self.pull_request_version |
|
|
3828 | ||
|
3829 |
return self.pull_request_version |
|
|
3844 | return self.pull_request_version != version | |
|
3845 | ||
|
3846 | return self.pull_request_version < version | |
|
3847 | ||
|
3848 | def older_than_version_js(self, version): | |
|
3849 | """ | |
|
3850 | Checks if comment is made from previous version than given | |
|
3851 | """ | |
|
3852 | return json.dumps(self.older_than_version(version)) | |
|
3830 | 3853 | |
|
3831 | 3854 | @property |
|
3832 | 3855 | def commit_id(self): |
@@ -3843,7 +3866,9 b' class ChangesetComment(Base, BaseModel):' | |||
|
3843 | 3866 | |
|
3844 | 3867 | @property |
|
3845 | 3868 | def is_inline(self): |
|
3846 |
|
|
|
3869 | if self.line_no and self.f_path: | |
|
3870 | return True | |
|
3871 | return False | |
|
3847 | 3872 | |
|
3848 | 3873 | @property |
|
3849 | 3874 | def last_version(self): |
@@ -3856,6 +3881,16 b' class ChangesetComment(Base, BaseModel):' | |||
|
3856 | 3881 | return self.get_index_from_version( |
|
3857 | 3882 | self.pull_request_version_id, versions) |
|
3858 | 3883 | |
|
3884 | @property | |
|
3885 | def review_status(self): | |
|
3886 | if self.status_change: | |
|
3887 | return self.status_change[0].status | |
|
3888 | ||
|
3889 | @property | |
|
3890 | def review_status_lbl(self): | |
|
3891 | if self.status_change: | |
|
3892 | return self.status_change[0].status_lbl | |
|
3893 | ||
|
3859 | 3894 | def __repr__(self): |
|
3860 | 3895 | if self.comment_id: |
|
3861 | 3896 | return '<DB:Comment #%s>' % self.comment_id |
@@ -4134,6 +4169,23 b' class _PullRequestBase(BaseModel):' | |||
|
4134 | 4169 | return json.dumps(self.reviewer_data) |
|
4135 | 4170 | |
|
4136 | 4171 | @property |
|
4172 | def last_merge_metadata_parsed(self): | |
|
4173 | metadata = {} | |
|
4174 | if not self.last_merge_metadata: | |
|
4175 | return metadata | |
|
4176 | ||
|
4177 | if hasattr(self.last_merge_metadata, 'de_coerce'): | |
|
4178 | for k, v in self.last_merge_metadata.de_coerce().items(): | |
|
4179 | if k in ['target_ref', 'source_ref']: | |
|
4180 | metadata[k] = Reference(v['type'], v['name'], v['commit_id']) | |
|
4181 | else: | |
|
4182 | if hasattr(v, 'de_coerce'): | |
|
4183 | metadata[k] = v.de_coerce() | |
|
4184 | else: | |
|
4185 | metadata[k] = v | |
|
4186 | return metadata | |
|
4187 | ||
|
4188 | @property | |
|
4137 | 4189 | def work_in_progress(self): |
|
4138 | 4190 | """checks if pull request is work in progress by checking the title""" |
|
4139 | 4191 | title = self.title.upper() |
@@ -4306,6 +4358,7 b' class PullRequest(Base, _PullRequestBase' | |||
|
4306 | 4358 | __table_args__ = ( |
|
4307 | 4359 | base_table_args, |
|
4308 | 4360 | ) |
|
4361 | LATEST_VER = 'latest' | |
|
4309 | 4362 | |
|
4310 | 4363 | pull_request_id = Column( |
|
4311 | 4364 | 'pull_request_id', Integer(), nullable=False, primary_key=True) |
@@ -4364,6 +4417,10 b' class PullRequest(Base, _PullRequestBase' | |||
|
4364 | 4417 | def pull_request_version_id(self): |
|
4365 | 4418 | return getattr(pull_request_obj, 'pull_request_version_id', None) |
|
4366 | 4419 | |
|
4420 | @property | |
|
4421 | def pull_request_last_version(self): | |
|
4422 | return pull_request_obj.pull_request_last_version | |
|
4423 | ||
|
4367 | 4424 | attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False)) |
|
4368 | 4425 | |
|
4369 | 4426 | attrs.author = StrictAttributeDict( |
@@ -4428,6 +4485,10 b' class PullRequest(Base, _PullRequestBase' | |||
|
4428 | 4485 | """ |
|
4429 | 4486 | return self.versions.count() + 1 |
|
4430 | 4487 | |
|
4488 | @property | |
|
4489 | def pull_request_last_version(self): | |
|
4490 | return self.versions_count | |
|
4491 | ||
|
4431 | 4492 | |
|
4432 | 4493 | class PullRequestVersion(Base, _PullRequestBase): |
|
4433 | 4494 | __tablename__ = 'pull_request_versions' |
@@ -4475,6 +4536,8 b' class PullRequestReviewers(Base, BaseMod' | |||
|
4475 | 4536 | __table_args__ = ( |
|
4476 | 4537 | base_table_args, |
|
4477 | 4538 | ) |
|
4539 | ROLE_REVIEWER = u'reviewer' | |
|
4540 | ROLE_OBSERVER = u'observer' | |
|
4478 | 4541 | |
|
4479 | 4542 | @hybrid_property |
|
4480 | 4543 | def reasons(self): |
@@ -4502,6 +4565,8 b' class PullRequestReviewers(Base, BaseMod' | |||
|
4502 | 4565 | JsonType('list', dialect_map=dict(mysql=UnicodeText(16384))))) |
|
4503 | 4566 | |
|
4504 | 4567 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) |
|
4568 | role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER) | |
|
4569 | ||
|
4505 | 4570 | user = relationship('User') |
|
4506 | 4571 | pull_request = relationship('PullRequest') |
|
4507 | 4572 | |
@@ -5425,7 +5490,10 b' class FileStore(Base, BaseModel):' | |||
|
5425 | 5490 | repo_group = relationship('RepoGroup', lazy='joined') |
|
5426 | 5491 | |
|
5427 | 5492 | @classmethod |
|
5428 | def get_by_store_uid(cls, file_store_uid): | |
|
5493 | def get_by_store_uid(cls, file_store_uid, safe=False): | |
|
5494 | if safe: | |
|
5495 | return FileStore.query().filter(FileStore.file_uid == file_store_uid).first() | |
|
5496 | else: | |
|
5429 | 5497 | return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar() |
|
5430 | 5498 | |
|
5431 | 5499 | @classmethod |
@@ -1600,7 +1600,7 b' class PullRequestModel(BaseModel):' | |||
|
1600 | 1600 | 'source_ref': pull_request.source_ref_parts, |
|
1601 | 1601 | } |
|
1602 | 1602 | if pull_request.last_merge_metadata: |
|
1603 | metadata.update(pull_request.last_merge_metadata) | |
|
1603 | metadata.update(pull_request.last_merge_metadata_parsed) | |
|
1604 | 1604 | |
|
1605 | 1605 | if not possible and target_ref.type == 'branch': |
|
1606 | 1606 | # NOTE(marcink): case for mercurial multiple heads on branch |
@@ -55,3 +55,16 b'' | |||
|
55 | 55 | margin: 0 auto 35px auto; |
|
56 | 56 | } |
|
57 | 57 | } |
|
58 | ||
|
59 | .alert-text-success { | |
|
60 | color: @alert1; | |
|
61 | ||
|
62 | } | |
|
63 | ||
|
64 | .alert-text-error { | |
|
65 | color: @alert2; | |
|
66 | } | |
|
67 | ||
|
68 | .alert-text-warning { | |
|
69 | color: @alert3; | |
|
70 | } |
@@ -254,7 +254,7 b' input[type="button"] {' | |||
|
254 | 254 | |
|
255 | 255 | .btn-group-actions { |
|
256 | 256 | position: relative; |
|
257 |
z-index: |
|
|
257 | z-index: 50; | |
|
258 | 258 | |
|
259 | 259 | &:not(.open) .btn-action-switcher-container { |
|
260 | 260 | display: none; |
@@ -1078,10 +1078,16 b' input.filediff-collapse-state {' | |||
|
1078 | 1078 | background: @color5; |
|
1079 | 1079 | color: white; |
|
1080 | 1080 | } |
|
1081 | ||
|
1081 | 1082 | &[op="comments"] { /* comments on file */ |
|
1082 | 1083 | background: @grey4; |
|
1083 | 1084 | color: white; |
|
1084 | 1085 | } |
|
1086 | ||
|
1087 | &[op="options"] { /* context menu */ | |
|
1088 | background: @grey6; | |
|
1089 | color: black; | |
|
1090 | } | |
|
1085 | 1091 | } |
|
1086 | 1092 | } |
|
1087 | 1093 |
@@ -31,6 +31,10 b' a { cursor: pointer; }' | |||
|
31 | 31 | clear: both; |
|
32 | 32 | } |
|
33 | 33 | |
|
34 | .display-none { | |
|
35 | display: none; | |
|
36 | } | |
|
37 | ||
|
34 | 38 | .pull-right { |
|
35 | 39 | float: right !important; |
|
36 | 40 | } |
@@ -240,14 +240,14 b' div.markdown-block ol {' | |||
|
240 | 240 | div.markdown-block ul.checkbox li, |
|
241 | 241 | div.markdown-block ol.checkbox li { |
|
242 | 242 | list-style: none !important; |
|
243 |
margin: |
|
|
243 | margin: 0px !important; | |
|
244 | 244 | padding: 0 !important; |
|
245 | 245 | } |
|
246 | 246 | |
|
247 | 247 | div.markdown-block ul li, |
|
248 | 248 | div.markdown-block ol li { |
|
249 | 249 | list-style: disc !important; |
|
250 |
margin: |
|
|
250 | margin: 0px !important; | |
|
251 | 251 | padding: 0 !important; |
|
252 | 252 | } |
|
253 | 253 |
@@ -83,6 +83,11 b' body {' | |||
|
83 | 83 | } |
|
84 | 84 | } |
|
85 | 85 | |
|
86 | .flex-container { | |
|
87 | display: flex; | |
|
88 | justify-content: space-between; | |
|
89 | } | |
|
90 | ||
|
86 | 91 | .action-link{ |
|
87 | 92 | margin-left: @padding; |
|
88 | 93 | padding-left: @padding; |
@@ -482,10 +487,15 b' ul.auth_plugins {' | |||
|
482 | 487 | text-align: left; |
|
483 | 488 | overflow: hidden; |
|
484 | 489 | white-space: pre-line; |
|
485 | } | |
|
486 | ||
|
487 | .pr-details-title { | |
|
488 | height: 16px | |
|
490 | padding-top: 5px | |
|
491 | } | |
|
492 | ||
|
493 | #add_reviewer { | |
|
494 | padding-top: 10px; | |
|
495 | } | |
|
496 | ||
|
497 | #add_reviewer_input { | |
|
498 | padding-top: 10px | |
|
489 | 499 | } |
|
490 | 500 | |
|
491 | 501 | .pr-details-title-author-pref { |
@@ -1173,9 +1183,12 b' label {' | |||
|
1173 | 1183 | a { |
|
1174 | 1184 | color: @grey5 |
|
1175 | 1185 | } |
|
1176 | @media screen and (max-width: 1200px) { | |
|
1186 | ||
|
1187 | // 1024px or smaller | |
|
1188 | @media screen and (max-width: 1180px) { | |
|
1177 | 1189 | display: none; |
|
1178 | 1190 | } |
|
1191 | ||
|
1179 | 1192 | } |
|
1180 | 1193 | |
|
1181 | 1194 | img { |
@@ -1492,26 +1505,17 b' table.integrations {' | |||
|
1492 | 1505 | |
|
1493 | 1506 | // Pull Requests |
|
1494 | 1507 | .summary-details { |
|
1495 |
width: |
|
|
1508 | width: 100%; | |
|
1496 | 1509 | } |
|
1497 | 1510 | .pr-summary { |
|
1498 | 1511 | border-bottom: @border-thickness solid @grey5; |
|
1499 | 1512 | margin-bottom: @space; |
|
1500 | 1513 | } |
|
1501 | 1514 | |
|
1502 | .reviewers-title { | |
|
1503 | width: 25%; | |
|
1504 | min-width: 200px; | |
|
1505 | ||
|
1506 | &.first-panel { | |
|
1507 | margin-top: 34px; | |
|
1508 | } | |
|
1509 | } | |
|
1510 | ||
|
1511 | 1515 | .reviewers { |
|
1512 |
|
|
|
1513 | min-width: 200px; | |
|
1514 | } | |
|
1516 | width: 98%; | |
|
1517 | } | |
|
1518 | ||
|
1515 | 1519 | .reviewers ul li { |
|
1516 | 1520 | position: relative; |
|
1517 | 1521 | width: 100%; |
@@ -1523,18 +1527,14 b' table.integrations {' | |||
|
1523 | 1527 | min-height: 55px; |
|
1524 | 1528 | } |
|
1525 | 1529 | |
|
1526 | .reviewers_member { | |
|
1527 | width: 100%; | |
|
1528 | overflow: auto; | |
|
1529 | } | |
|
1530 | 1530 | .reviewer_reason { |
|
1531 | 1531 | padding-left: 20px; |
|
1532 | 1532 | line-height: 1.5em; |
|
1533 | 1533 | } |
|
1534 | 1534 | .reviewer_status { |
|
1535 | 1535 | display: inline-block; |
|
1536 |
width: 2 |
|
|
1537 |
min-width: 2 |
|
|
1536 | width: 20px; | |
|
1537 | min-width: 20px; | |
|
1538 | 1538 | height: 1.2em; |
|
1539 | 1539 | line-height: 1em; |
|
1540 | 1540 | } |
@@ -1557,25 +1557,20 b' table.integrations {' | |||
|
1557 | 1557 | } |
|
1558 | 1558 | |
|
1559 | 1559 | .reviewer_member_mandatory { |
|
1560 | position: absolute; | |
|
1561 | left: 15px; | |
|
1562 | top: 8px; | |
|
1563 | 1560 | width: 16px; |
|
1564 | 1561 | font-size: 11px; |
|
1565 | 1562 | margin: 0; |
|
1566 | 1563 | padding: 0; |
|
1567 | 1564 | color: black; |
|
1565 | opacity: 0.4; | |
|
1568 | 1566 | } |
|
1569 | 1567 | |
|
1570 | 1568 | .reviewer_member_mandatory_remove, |
|
1571 | 1569 | .reviewer_member_remove { |
|
1572 | position: absolute; | |
|
1573 | right: 0; | |
|
1574 | top: 0; | |
|
1575 | 1570 | width: 16px; |
|
1576 | margin-bottom: 10px; | |
|
1577 | 1571 | padding: 0; |
|
1578 | 1572 | color: black; |
|
1573 | cursor: pointer; | |
|
1579 | 1574 | } |
|
1580 | 1575 | |
|
1581 | 1576 | .reviewer_member_mandatory_remove { |
@@ -1593,6 +1588,9 b' table.integrations {' | |||
|
1593 | 1588 | cursor: pointer; |
|
1594 | 1589 | } |
|
1595 | 1590 | .pr-details-title { |
|
1591 | height: 20px; | |
|
1592 | line-height: 20px; | |
|
1593 | ||
|
1596 | 1594 | padding-bottom: 8px; |
|
1597 | 1595 | border-bottom: @border-thickness solid @grey5; |
|
1598 | 1596 | |
@@ -1617,7 +1615,7 b' table.integrations {' | |||
|
1617 | 1615 | text-decoration: line-through; |
|
1618 | 1616 | } |
|
1619 | 1617 | |
|
1620 | .todo-table { | |
|
1618 | .todo-table, .comments-table { | |
|
1621 | 1619 | width: 100%; |
|
1622 | 1620 | |
|
1623 | 1621 | td { |
@@ -1627,7 +1625,8 b' table.integrations {' | |||
|
1627 | 1625 | .td-todo-number { |
|
1628 | 1626 | text-align: left; |
|
1629 | 1627 | white-space: nowrap; |
|
1630 |
width: 1 |
|
|
1628 | width: 1%; | |
|
1629 | padding-right: 2px; | |
|
1631 | 1630 | } |
|
1632 | 1631 | |
|
1633 | 1632 | .td-todo-gravatar { |
@@ -1651,10 +1650,13 b' table.integrations {' | |||
|
1651 | 1650 | text-overflow: ellipsis; |
|
1652 | 1651 | } |
|
1653 | 1652 | |
|
1653 | table.group_members { | |
|
1654 | width: 100% | |
|
1655 | } | |
|
1656 | ||
|
1654 | 1657 | .group_members { |
|
1655 | 1658 | margin-top: 0; |
|
1656 | 1659 | padding: 0; |
|
1657 | list-style: outside none none; | |
|
1658 | 1660 | |
|
1659 | 1661 | img { |
|
1660 | 1662 | height: @gravatar-size; |
@@ -1698,7 +1700,7 b' table.integrations {' | |||
|
1698 | 1700 | } |
|
1699 | 1701 | |
|
1700 | 1702 | .reviewer_ac .ac-input { |
|
1701 |
width: |
|
|
1703 | width: 100%; | |
|
1702 | 1704 | margin-bottom: 1em; |
|
1703 | 1705 | } |
|
1704 | 1706 | |
@@ -2772,7 +2774,7 b' table.rctable td.td-search-results div {' | |||
|
2772 | 2774 | } |
|
2773 | 2775 | |
|
2774 | 2776 | #help_kb .modal-content{ |
|
2775 |
max-width: |
|
|
2777 | max-width: 800px; | |
|
2776 | 2778 | margin: 10% auto; |
|
2777 | 2779 | |
|
2778 | 2780 | table{ |
@@ -3069,4 +3071,141 b' form.markup-form {' | |||
|
3069 | 3071 | |
|
3070 | 3072 | .pr-hovercard-title { |
|
3071 | 3073 | padding-top: 5px; |
|
3072 | } No newline at end of file | |
|
3074 | } | |
|
3075 | ||
|
3076 | .action-divider { | |
|
3077 | opacity: 0.5; | |
|
3078 | } | |
|
3079 | ||
|
3080 | .details-inline-block { | |
|
3081 | display: inline-block; | |
|
3082 | position: relative; | |
|
3083 | } | |
|
3084 | ||
|
3085 | .details-inline-block summary { | |
|
3086 | list-style: none; | |
|
3087 | } | |
|
3088 | ||
|
3089 | details:not([open]) > :not(summary) { | |
|
3090 | display: none !important; | |
|
3091 | } | |
|
3092 | ||
|
3093 | .details-reset > summary { | |
|
3094 | list-style: none; | |
|
3095 | } | |
|
3096 | ||
|
3097 | .details-reset > summary::-webkit-details-marker { | |
|
3098 | display: none; | |
|
3099 | } | |
|
3100 | ||
|
3101 | .details-dropdown { | |
|
3102 | position: absolute; | |
|
3103 | top: 100%; | |
|
3104 | width: 185px; | |
|
3105 | list-style: none; | |
|
3106 | background-color: #fff; | |
|
3107 | background-clip: padding-box; | |
|
3108 | border: 1px solid @grey5; | |
|
3109 | box-shadow: 0 8px 24px rgba(149, 157, 165, .2); | |
|
3110 | left: -150px; | |
|
3111 | text-align: left; | |
|
3112 | z-index: 90; | |
|
3113 | } | |
|
3114 | ||
|
3115 | .dropdown-divider { | |
|
3116 | display: block; | |
|
3117 | height: 0; | |
|
3118 | margin: 8px 0; | |
|
3119 | border-top: 1px solid @grey5; | |
|
3120 | } | |
|
3121 | ||
|
3122 | .dropdown-item { | |
|
3123 | display: block; | |
|
3124 | padding: 4px 8px 4px 16px; | |
|
3125 | overflow: hidden; | |
|
3126 | text-overflow: ellipsis; | |
|
3127 | white-space: nowrap; | |
|
3128 | font-weight: normal; | |
|
3129 | } | |
|
3130 | ||
|
3131 | .right-sidebar { | |
|
3132 | position: fixed; | |
|
3133 | top: 0px; | |
|
3134 | bottom: 0; | |
|
3135 | right: 0; | |
|
3136 | ||
|
3137 | background: #fafafa; | |
|
3138 | z-index: 50; | |
|
3139 | } | |
|
3140 | ||
|
3141 | .right-sidebar { | |
|
3142 | border-left: 1px solid @grey5; | |
|
3143 | } | |
|
3144 | ||
|
3145 | .right-sidebar.right-sidebar-expanded { | |
|
3146 | width: 300px; | |
|
3147 | overflow: scroll; | |
|
3148 | } | |
|
3149 | ||
|
3150 | .right-sidebar.right-sidebar-collapsed { | |
|
3151 | width: 40px; | |
|
3152 | padding: 0; | |
|
3153 | display: block; | |
|
3154 | overflow: hidden; | |
|
3155 | } | |
|
3156 | ||
|
3157 | .sidenav { | |
|
3158 | float: right; | |
|
3159 | will-change: min-height; | |
|
3160 | background: #fafafa; | |
|
3161 | width: 100%; | |
|
3162 | } | |
|
3163 | ||
|
3164 | .sidebar-toggle { | |
|
3165 | height: 30px; | |
|
3166 | text-align: center; | |
|
3167 | margin: 15px 0px 0 0; | |
|
3168 | } | |
|
3169 | ||
|
3170 | .sidebar-toggle a { | |
|
3171 | ||
|
3172 | } | |
|
3173 | ||
|
3174 | .sidebar-content { | |
|
3175 | margin-left: 15px; | |
|
3176 | margin-right: 15px; | |
|
3177 | } | |
|
3178 | ||
|
3179 | .sidebar-heading { | |
|
3180 | font-size: 1.2em; | |
|
3181 | font-weight: 700; | |
|
3182 | margin-top: 10px; | |
|
3183 | } | |
|
3184 | ||
|
3185 | .sidebar-element { | |
|
3186 | margin-top: 20px; | |
|
3187 | } | |
|
3188 | ||
|
3189 | .right-sidebar-collapsed-state { | |
|
3190 | display: flex; | |
|
3191 | flex-direction: column; | |
|
3192 | justify-content: center; | |
|
3193 | align-items: center; | |
|
3194 | padding: 0 10px; | |
|
3195 | cursor: pointer; | |
|
3196 | font-size: 1.3em; | |
|
3197 | margin: 0 -15px; | |
|
3198 | } | |
|
3199 | ||
|
3200 | .right-sidebar-collapsed-state:hover { | |
|
3201 | background-color: @grey5; | |
|
3202 | } | |
|
3203 | ||
|
3204 | .old-comments-marker { | |
|
3205 | text-align: left; | |
|
3206 | } | |
|
3207 | ||
|
3208 | .old-comments-marker td { | |
|
3209 | padding-top: 15px; | |
|
3210 | border-bottom: 1px solid @grey5; | |
|
3211 | } |
@@ -790,7 +790,7 b' input {' | |||
|
790 | 790 | |
|
791 | 791 | &.main_filter_input { |
|
792 | 792 | padding: 5px 10px; |
|
793 | min-width: 340px; | |
|
793 | ||
|
794 | 794 | color: @grey7; |
|
795 | 795 | background: @black; |
|
796 | 796 | min-height: 18px; |
@@ -800,11 +800,34 b' input {' | |||
|
800 | 800 | color: @grey2 !important; |
|
801 | 801 | background: white !important; |
|
802 | 802 | } |
|
803 | ||
|
803 | 804 | &:focus { |
|
804 | 805 | color: @grey2 !important; |
|
805 | 806 | background: white !important; |
|
806 | 807 | } |
|
808 | ||
|
809 | min-width: 360px; | |
|
810 | ||
|
811 | @media screen and (max-width: 1600px) { | |
|
812 | min-width: 300px; | |
|
807 | 813 | } |
|
814 | @media screen and (max-width: 1500px) { | |
|
815 | min-width: 280px; | |
|
816 | } | |
|
817 | @media screen and (max-width: 1400px) { | |
|
818 | min-width: 260px; | |
|
819 | } | |
|
820 | @media screen and (max-width: 1300px) { | |
|
821 | min-width: 240px; | |
|
822 | } | |
|
823 | @media screen and (max-width: 1200px) { | |
|
824 | min-width: 220px; | |
|
825 | } | |
|
826 | @media screen and (max-width: 720px) { | |
|
827 | min-width: 140px; | |
|
828 | } | |
|
829 | } | |
|
830 | ||
|
808 | 831 | } |
|
809 | 832 | |
|
810 | 833 |
@@ -168,6 +168,7 b'' | |||
|
168 | 168 | .icon-remove:before { content: '\e810'; } /* 'ξ ' */ |
|
169 | 169 | .icon-fork:before { content: '\e811'; } /* 'ξ ' */ |
|
170 | 170 | .icon-more:before { content: '\e812'; } /* 'ξ ' */ |
|
171 | .icon-options:before { content: '\e812'; } /* 'ξ ' */ | |
|
171 | 172 | .icon-search:before { content: '\e813'; } /* 'ξ ' */ |
|
172 | 173 | .icon-scissors:before { content: '\e814'; } /* 'ξ ' */ |
|
173 | 174 | .icon-download:before { content: '\e815'; } /* 'ξ ' */ |
@@ -251,6 +252,7 b'' | |||
|
251 | 252 | // TRANSFORM |
|
252 | 253 | .icon-merge:before {transform: rotate(180deg);} |
|
253 | 254 | .icon-wide-mode:before {transform: rotate(90deg);} |
|
255 | .icon-options:before {transform: rotate(90deg);} | |
|
254 | 256 | |
|
255 | 257 | // -- END ICON CLASSES -- // |
|
256 | 258 |
@@ -131,6 +131,11 b' function setRCMouseBindings(repoName, re' | |||
|
131 | 131 | window.location = pyroutes.url( |
|
132 | 132 | 'edit_repo_perms', {'repo_name': repoName}); |
|
133 | 133 | }); |
|
134 | Mousetrap.bind(['t s'], function(e) { | |
|
135 | if (window.toggleSidebar !== undefined) { | |
|
136 | window.toggleSidebar(); | |
|
137 | } | |
|
138 | }); | |
|
134 | 139 | } |
|
135 | 140 | } |
|
136 | 141 |
@@ -246,6 +246,8 b' function registerRCRoutes() {' | |||
|
246 | 246 | pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']); |
|
247 | 247 | pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']); |
|
248 | 248 | pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']); |
|
249 | pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']); | |
|
250 | pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']); | |
|
249 | 251 | pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']); |
|
250 | 252 | pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']); |
|
251 | 253 | pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']); |
@@ -28,9 +28,12 b' export class RhodecodeApp extends Polyme' | |||
|
28 | 28 | super.connectedCallback(); |
|
29 | 29 | ccLog.debug('rhodeCodeApp created'); |
|
30 | 30 | $.Topic('/notifications').subscribe(this.handleNotifications.bind(this)); |
|
31 | $.Topic('/comment').subscribe(this.handleComment.bind(this)); | |
|
31 | 32 | $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this)); |
|
32 | 33 | $.Topic('/connection_controller/subscribe').subscribe( |
|
33 |
this.subscribeToChannelTopic.bind(this) |
|
|
34 | this.subscribeToChannelTopic.bind(this) | |
|
35 | ); | |
|
36 | ||
|
34 | 37 | // this event can be used to coordinate plugins to do their |
|
35 | 38 | // initialization before channelstream is kicked off |
|
36 | 39 | $.Topic('/__MAIN_APP__').publish({}); |
@@ -71,6 +74,14 b' export class RhodecodeApp extends Polyme' | |||
|
71 | 74 | |
|
72 | 75 | } |
|
73 | 76 | |
|
77 | handleComment(data) { | |
|
78 | if (data.message.comment_id) { | |
|
79 | if (window.refreshAllComments !== undefined) { | |
|
80 | refreshAllComments() | |
|
81 | } | |
|
82 | } | |
|
83 | } | |
|
84 | ||
|
74 | 85 | faviconUpdate(data) { |
|
75 | 86 | this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count; |
|
76 | 87 | } |
@@ -95,6 +106,7 b' export class RhodecodeApp extends Polyme' | |||
|
95 | 106 | } |
|
96 | 107 | // append any additional channels registered in other plugins |
|
97 | 108 | $.Topic('/connection_controller/subscribe').processPrepared(); |
|
109 | ||
|
98 | 110 | channelstreamConnection.connect(); |
|
99 | 111 | } |
|
100 | 112 | } |
@@ -157,8 +169,7 b' export class RhodecodeApp extends Polyme' | |||
|
157 | 169 | |
|
158 | 170 | handleConnected(event) { |
|
159 | 171 | var channelstreamConnection = this.getChannelStreamConnection(); |
|
160 | channelstreamConnection.set('channelsState', | |
|
161 | event.detail.channels_info); | |
|
172 | channelstreamConnection.set('channelsState', event.detail.channels_info); | |
|
162 | 173 | channelstreamConnection.set('userState', event.detail.state); |
|
163 | 174 | channelstreamConnection.set('channels', event.detail.channels); |
|
164 | 175 | this.propagageChannelsState(); |
@@ -299,13 +299,22 b' var tooltipActivate = function () {' | |||
|
299 | 299 | var altHovercard =$origin.data('hovercardAlt'); |
|
300 | 300 | |
|
301 | 301 | if (hovercardUrl !== undefined && hovercardUrl !== "") { |
|
302 | var urlLoad = true; | |
|
302 | 303 | if (hovercardUrl.substr(0,12) === 'pyroutes.url'){ |
|
303 | 304 | hovercardUrl = eval(hovercardUrl) |
|
305 | } else if (hovercardUrl.substr(0, 11) === 'javascript:') { | |
|
306 | var jsFunc = hovercardUrl.substr(11); | |
|
307 | urlLoad = false; | |
|
308 | loaded = true; | |
|
309 | instance.content(eval(jsFunc)) | |
|
304 | 310 | } |
|
305 | 311 | |
|
312 | if (urlLoad) { | |
|
306 | 313 | var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) { |
|
307 | 314 | instance.content(data); |
|
308 | 315 | }) |
|
316 | } | |
|
317 | ||
|
309 | 318 | } else { |
|
310 | 319 | if ($origin.data('hovercardAltHtml')) { |
|
311 | 320 | var data = atob($origin.data('hovercardAltHtml')); |
@@ -677,7 +686,9 b' var feedLifetimeOptions = function(query' | |||
|
677 | 686 | query.callback(data); |
|
678 | 687 | }; |
|
679 | 688 | |
|
680 | ||
|
689 | /* | |
|
690 | * Retrievew via templateContext.session_attrs.key | |
|
691 | * */ | |
|
681 | 692 | var storeUserSessionAttr = function (key, val) { |
|
682 | 693 | |
|
683 | 694 | var postData = { |
@@ -670,8 +670,20 b' var CommentsController = function() {' | |||
|
670 | 670 | |
|
671 | 671 | var success = function(response) { |
|
672 | 672 | $comment.remove(); |
|
673 | ||
|
674 | if (window.updateSticky !== undefined) { | |
|
675 | // potentially our comments change the active window size, so we | |
|
676 | // notify sticky elements | |
|
677 | updateSticky() | |
|
678 | } | |
|
679 | ||
|
680 | if (window.refreshAllComments !== undefined) { | |
|
681 | // if we have this handler, run it, and refresh all comments boxes | |
|
682 | refreshAllComments() | |
|
683 | } | |
|
673 | 684 | return false; |
|
674 | 685 | }; |
|
686 | ||
|
675 | 687 | var failure = function(jqXHR, textStatus, errorThrown) { |
|
676 | 688 | var prefix = "Error while deleting this comment.\n" |
|
677 | 689 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
@@ -682,6 +694,9 b' var CommentsController = function() {' | |||
|
682 | 694 | return false; |
|
683 | 695 | }; |
|
684 | 696 | ajaxPOST(url, postData, success, failure); |
|
697 | ||
|
698 | ||
|
699 | ||
|
685 | 700 | } |
|
686 | 701 | |
|
687 | 702 | this.deleteComment = function(node) { |
@@ -727,6 +742,15 b' var CommentsController = function() {' | |||
|
727 | 742 | $filediff.find('.hide-line-comments').removeClass('hide-line-comments'); |
|
728 | 743 | $filediff.toggleClass('hide-comments'); |
|
729 | 744 | } |
|
745 | ||
|
746 | // since we change the height of the diff container that has anchor points for upper | |
|
747 | // sticky header, we need to tell it to re-calculate those | |
|
748 | if (window.updateSticky !== undefined) { | |
|
749 | // potentially our comments change the active window size, so we | |
|
750 | // notify sticky elements | |
|
751 | updateSticky() | |
|
752 | } | |
|
753 | ||
|
730 | 754 | return false; |
|
731 | 755 | }; |
|
732 | 756 | |
@@ -747,7 +771,7 b' var CommentsController = function() {' | |||
|
747 | 771 | var cm = commentForm.getCmInstance(); |
|
748 | 772 | |
|
749 | 773 | if (resolvesCommentId){ |
|
750 |
|
|
|
774 | placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId); | |
|
751 | 775 | } |
|
752 | 776 | |
|
753 | 777 | setTimeout(function() { |
@@ -1077,9 +1101,15 b' var CommentsController = function() {' | |||
|
1077 | 1101 | updateSticky() |
|
1078 | 1102 | } |
|
1079 | 1103 | |
|
1104 | if (window.refreshAllComments !== undefined) { | |
|
1105 | // if we have this handler, run it, and refresh all comments boxes | |
|
1106 | refreshAllComments() | |
|
1107 | } | |
|
1108 | ||
|
1080 | 1109 | commentForm.setActionButtonsDisabled(false); |
|
1081 | 1110 | |
|
1082 | 1111 | }; |
|
1112 | ||
|
1083 | 1113 | var submitFailCallback = function(jqXHR, textStatus, errorThrown) { |
|
1084 | 1114 | var prefix = "Error while editing comment.\n" |
|
1085 | 1115 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
@@ -1209,6 +1239,11 b' var CommentsController = function() {' | |||
|
1209 | 1239 | updateSticky() |
|
1210 | 1240 | } |
|
1211 | 1241 | |
|
1242 | if (window.refreshAllComments !== undefined) { | |
|
1243 | // if we have this handler, run it, and refresh all comments boxes | |
|
1244 | refreshAllComments() | |
|
1245 | } | |
|
1246 | ||
|
1212 | 1247 | commentForm.setActionButtonsDisabled(false); |
|
1213 | 1248 | |
|
1214 | 1249 | }; |
@@ -35,4 +35,75 b' var quick_repo_menu = function() {' | |||
|
35 | 35 | }, function() { |
|
36 | 36 | hide_quick_repo_menus(); |
|
37 | 37 | }); |
|
38 | }; No newline at end of file | |
|
38 | }; | |
|
39 | ||
|
40 | ||
|
41 | window.toggleElement = function (elem, target) { | |
|
42 | var $elem = $(elem); | |
|
43 | var $target = $(target); | |
|
44 | ||
|
45 | if ($target.is(':visible') || $target.length === 0) { | |
|
46 | $target.hide(); | |
|
47 | $elem.html($elem.data('toggleOn')) | |
|
48 | } else { | |
|
49 | $target.show(); | |
|
50 | $elem.html($elem.data('toggleOff')) | |
|
51 | } | |
|
52 | ||
|
53 | return false | |
|
54 | } | |
|
55 | ||
|
56 | var marginExpVal = '300' // needs a sync with `.right-sidebar.right-sidebar-expanded` value | |
|
57 | var marginColVal = '40' // needs a sync with `.right-sidebar.right-sidebar-collapsed` value | |
|
58 | ||
|
59 | var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)}; | |
|
60 | var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)}; | |
|
61 | ||
|
62 | var updateStickyHeader = function () { | |
|
63 | if (window.updateSticky !== undefined) { | |
|
64 | // potentially our comments change the active window size, so we | |
|
65 | // notify sticky elements | |
|
66 | updateSticky() | |
|
67 | } | |
|
68 | } | |
|
69 | ||
|
70 | var expandSidebar = function () { | |
|
71 | var $sideBar = $('.right-sidebar'); | |
|
72 | $('.outerwrapper').css(marginExpanded); | |
|
73 | $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>'); | |
|
74 | $('.right-sidebar-collapsed-state').hide(); | |
|
75 | $('.right-sidebar-expanded-state').show(); | |
|
76 | $('.branding').addClass('display-none'); | |
|
77 | $sideBar.addClass('right-sidebar-expanded') | |
|
78 | $sideBar.removeClass('right-sidebar-collapsed') | |
|
79 | } | |
|
80 | ||
|
81 | var collapseSidebar = function () { | |
|
82 | var $sideBar = $('.right-sidebar'); | |
|
83 | $('.outerwrapper').css(marginCollapsed); | |
|
84 | $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>'); | |
|
85 | $('.right-sidebar-collapsed-state').show(); | |
|
86 | $('.right-sidebar-expanded-state').hide(); | |
|
87 | $('.branding').removeClass('display-none'); | |
|
88 | $sideBar.removeClass('right-sidebar-expanded') | |
|
89 | $sideBar.addClass('right-sidebar-collapsed') | |
|
90 | } | |
|
91 | ||
|
92 | window.toggleSidebar = function () { | |
|
93 | var $sideBar = $('.right-sidebar'); | |
|
94 | ||
|
95 | if ($sideBar.hasClass('right-sidebar-expanded')) { | |
|
96 | // expanded -> collapsed transition | |
|
97 | collapseSidebar(); | |
|
98 | var sidebarState = 'collapsed'; | |
|
99 | ||
|
100 | } else { | |
|
101 | // collapsed -> expanded | |
|
102 | expandSidebar(); | |
|
103 | var sidebarState = 'expanded'; | |
|
104 | } | |
|
105 | ||
|
106 | // update our other sticky header in same context | |
|
107 | updateStickyHeader(); | |
|
108 | storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState); | |
|
109 | } |
@@ -98,10 +98,13 b' ReviewersController = function () {' | |||
|
98 | 98 | var self = this; |
|
99 | 99 | this.$reviewRulesContainer = $('#review_rules'); |
|
100 | 100 | this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules'); |
|
101 | this.$userRule = $('.pr-user-rule-container'); | |
|
101 | 102 | this.forbidReviewUsers = undefined; |
|
102 | 103 | this.$reviewMembers = $('#review_members'); |
|
103 | 104 | this.currentRequest = null; |
|
104 | 105 | this.diffData = null; |
|
106 | this.enabledRules = []; | |
|
107 | ||
|
105 | 108 | //dummy handler, we might register our own later |
|
106 | 109 | this.diffDataHandler = function(data){}; |
|
107 | 110 | |
@@ -116,14 +119,17 b' ReviewersController = function () {' | |||
|
116 | 119 | |
|
117 | 120 | this.hideReviewRules = function () { |
|
118 | 121 | self.$reviewRulesContainer.hide(); |
|
122 | $(self.$userRule.selector).hide(); | |
|
119 | 123 | }; |
|
120 | 124 | |
|
121 | 125 | this.showReviewRules = function () { |
|
122 | 126 | self.$reviewRulesContainer.show(); |
|
127 | $(self.$userRule.selector).show(); | |
|
123 | 128 | }; |
|
124 | 129 | |
|
125 | 130 | this.addRule = function (ruleText) { |
|
126 | 131 | self.showReviewRules(); |
|
132 | self.enabledRules.push(ruleText); | |
|
127 | 133 | return '<div>- {0}</div>'.format(ruleText) |
|
128 | 134 | }; |
|
129 | 135 | |
@@ -179,6 +185,7 b' ReviewersController = function () {' | |||
|
179 | 185 | _gettext('Reviewers picked from source code changes.')) |
|
180 | 186 | ) |
|
181 | 187 | } |
|
188 | ||
|
182 | 189 | if (data.rules.forbid_adding_reviewers) { |
|
183 | 190 | $('#add_reviewer_input').remove(); |
|
184 | 191 | self.$rulesList.append( |
@@ -186,6 +193,7 b' ReviewersController = function () {' | |||
|
186 | 193 | _gettext('Adding new reviewers is forbidden.')) |
|
187 | 194 | ) |
|
188 | 195 | } |
|
196 | ||
|
189 | 197 | if (data.rules.forbid_author_to_review) { |
|
190 | 198 | self.forbidReviewUsers.push(data.rules_data.pr_author); |
|
191 | 199 | self.$rulesList.append( |
@@ -193,6 +201,7 b' ReviewersController = function () {' | |||
|
193 | 201 | _gettext('Author is not allowed to be a reviewer.')) |
|
194 | 202 | ) |
|
195 | 203 | } |
|
204 | ||
|
196 | 205 | if (data.rules.forbid_commit_author_to_review) { |
|
197 | 206 | |
|
198 | 207 | if (data.rules_data.forbidden_users) { |
@@ -208,6 +217,12 b' ReviewersController = function () {' | |||
|
208 | 217 | ) |
|
209 | 218 | } |
|
210 | 219 | |
|
220 | // we don't have any rules set, so we inform users about it | |
|
221 | if (self.enabledRules.length === 0) { | |
|
222 | self.addRule( | |
|
223 | _gettext('No review rules set.')) | |
|
224 | } | |
|
225 | ||
|
211 | 226 | return self.forbidReviewUsers |
|
212 | 227 | }; |
|
213 | 228 | |
@@ -264,8 +279,11 b' ReviewersController = function () {' | |||
|
264 | 279 | $('#user').show(); // show user autocomplete after load |
|
265 | 280 | |
|
266 | 281 | var commitElements = data["diff_info"]['commits']; |
|
282 | ||
|
267 | 283 | if (commitElements.length === 0) { |
|
268 | prButtonLock(true, _gettext('no commits'), 'all'); | |
|
284 | var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format( | |
|
285 | _gettext('There are no commits to merge.')); | |
|
286 | prButtonLock(true, noCommitsMsg, 'all'); | |
|
269 | 287 | |
|
270 | 288 | } else { |
|
271 | 289 | // un-lock PR button, so we cannot send PR before it's calculated |
@@ -309,7 +327,6 b' ReviewersController = function () {' | |||
|
309 | 327 | }; |
|
310 | 328 | |
|
311 | 329 | this.addReviewMember = function (reviewer_obj, reasons, mandatory) { |
|
312 | var members = self.$reviewMembers.get(0); | |
|
313 | 330 | var id = reviewer_obj.user_id; |
|
314 | 331 | var username = reviewer_obj.username; |
|
315 | 332 | |
@@ -318,10 +335,10 b' ReviewersController = function () {' | |||
|
318 | 335 | |
|
319 | 336 | // register IDS to check if we don't have this ID already in |
|
320 | 337 | var currentIds = []; |
|
321 | var _els = self.$reviewMembers.find('li').toArray(); | |
|
322 | for (el in _els) { | |
|
323 |
currentIds.push( |
|
|
324 | } | |
|
338 | ||
|
339 | $.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) { | |
|
340 | currentIds.push($(value).data('reviewerUserId')) | |
|
341 | }) | |
|
325 | 342 | |
|
326 | 343 | var userAllowedReview = function (userId) { |
|
327 | 344 | var allowed = true; |
@@ -339,20 +356,23 b' ReviewersController = function () {' | |||
|
339 | 356 | alert(_gettext('User `{0}` not allowed to be a reviewer').format(username)); |
|
340 | 357 | } else { |
|
341 | 358 | // only add if it's not there |
|
342 |
var alreadyReviewer = currentIds.indexOf( |
|
|
359 | var alreadyReviewer = currentIds.indexOf(id) != -1; | |
|
343 | 360 | |
|
344 | 361 | if (alreadyReviewer) { |
|
345 | 362 | alert(_gettext('User `{0}` already in reviewers').format(username)); |
|
346 | 363 | } else { |
|
347 |
|
|
|
364 | var reviewerEntry = renderTemplate('reviewMemberEntry', { | |
|
348 | 365 | 'member': reviewer_obj, |
|
349 | 366 | 'mandatory': mandatory, |
|
367 | 'reasons': reasons, | |
|
350 | 368 | 'allowed_to_update': true, |
|
351 | 369 | 'review_status': 'not_reviewed', |
|
352 | 370 | 'review_status_label': _gettext('Not Reviewed'), |
|
353 |
' |
|
|
354 | 'create': true | |
|
355 | }); | |
|
371 | 'user_group': reviewer_obj.user_group, | |
|
372 | 'create': true, | |
|
373 | 'rule_show': true, | |
|
374 | }) | |
|
375 | $(self.$reviewMembers.selector).append(reviewerEntry); | |
|
356 | 376 | tooltipActivate(); |
|
357 | 377 | } |
|
358 | 378 | } |
@@ -476,7 +496,7 b' var ReviewerAutoComplete = function(inpu' | |||
|
476 | 496 | }; |
|
477 | 497 | |
|
478 | 498 | |
|
479 | VersionController = function () { | |
|
499 | window.VersionController = function () { | |
|
480 | 500 | var self = this; |
|
481 | 501 | this.$verSource = $('input[name=ver_source]'); |
|
482 | 502 | this.$verTarget = $('input[name=ver_target]'); |
@@ -596,25 +616,10 b' VersionController = function () {' | |||
|
596 | 616 | return false |
|
597 | 617 | }; |
|
598 | 618 | |
|
599 | this.toggleElement = function (elem, target) { | |
|
600 | var $elem = $(elem); | |
|
601 | var $target = $(target); | |
|
602 | ||
|
603 | if ($target.is(':visible')) { | |
|
604 | $target.hide(); | |
|
605 | $elem.html($elem.data('toggleOn')) | |
|
606 | } else { | |
|
607 | $target.show(); | |
|
608 | $elem.html($elem.data('toggleOff')) | |
|
609 | } | |
|
610 | ||
|
611 | return false | |
|
612 | } | |
|
613 | ||
|
614 | 619 | }; |
|
615 | 620 | |
|
616 | 621 | |
|
617 | UpdatePrController = function () { | |
|
622 | window.UpdatePrController = function () { | |
|
618 | 623 | var self = this; |
|
619 | 624 | this.$updateCommits = $('#update_commits'); |
|
620 | 625 | this.$updateCommitsSwitcher = $('#update_commits_switcher'); |
@@ -656,4 +661,230 b' UpdatePrController = function () {' | |||
|
656 | 661 | templateContext.repo_name, |
|
657 | 662 | templateContext.pull_request_data.pull_request_id, force); |
|
658 | 663 | }; |
|
659 | }; No newline at end of file | |
|
664 | }; | |
|
665 | ||
|
666 | /** | |
|
667 | * Reviewer display panel | |
|
668 | */ | |
|
669 | window.ReviewersPanel = { | |
|
670 | editButton: null, | |
|
671 | closeButton: null, | |
|
672 | addButton: null, | |
|
673 | removeButtons: null, | |
|
674 | reviewRules: null, | |
|
675 | setReviewers: null, | |
|
676 | ||
|
677 | setSelectors: function () { | |
|
678 | var self = this; | |
|
679 | self.editButton = $('#open_edit_reviewers'); | |
|
680 | self.closeButton =$('#close_edit_reviewers'); | |
|
681 | self.addButton = $('#add_reviewer'); | |
|
682 | self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove'); | |
|
683 | }, | |
|
684 | ||
|
685 | init: function (reviewRules, setReviewers) { | |
|
686 | var self = this; | |
|
687 | self.setSelectors(); | |
|
688 | ||
|
689 | this.reviewRules = reviewRules; | |
|
690 | this.setReviewers = setReviewers; | |
|
691 | ||
|
692 | this.editButton.on('click', function (e) { | |
|
693 | self.edit(); | |
|
694 | }); | |
|
695 | this.closeButton.on('click', function (e) { | |
|
696 | self.close(); | |
|
697 | self.renderReviewers(); | |
|
698 | }); | |
|
699 | ||
|
700 | self.renderReviewers(); | |
|
701 | ||
|
702 | }, | |
|
703 | ||
|
704 | renderReviewers: function () { | |
|
705 | ||
|
706 | $('#review_members').html('') | |
|
707 | $.each(this.setReviewers.reviewers, function (key, val) { | |
|
708 | var member = val; | |
|
709 | ||
|
710 | var entry = renderTemplate('reviewMemberEntry', { | |
|
711 | 'member': member, | |
|
712 | 'mandatory': member.mandatory, | |
|
713 | 'reasons': member.reasons, | |
|
714 | 'allowed_to_update': member.allowed_to_update, | |
|
715 | 'review_status': member.review_status, | |
|
716 | 'review_status_label': member.review_status_label, | |
|
717 | 'user_group': member.user_group, | |
|
718 | 'create': false | |
|
719 | }); | |
|
720 | ||
|
721 | $('#review_members').append(entry) | |
|
722 | }); | |
|
723 | tooltipActivate(); | |
|
724 | ||
|
725 | }, | |
|
726 | ||
|
727 | edit: function (event) { | |
|
728 | this.editButton.hide(); | |
|
729 | this.closeButton.show(); | |
|
730 | this.addButton.show(); | |
|
731 | $(this.removeButtons.selector).css('visibility', 'visible'); | |
|
732 | // review rules | |
|
733 | reviewersController.loadReviewRules(this.reviewRules); | |
|
734 | }, | |
|
735 | ||
|
736 | close: function (event) { | |
|
737 | this.editButton.show(); | |
|
738 | this.closeButton.hide(); | |
|
739 | this.addButton.hide(); | |
|
740 | $(this.removeButtons.selector).css('visibility', 'hidden'); | |
|
741 | // hide review rules | |
|
742 | reviewersController.hideReviewRules() | |
|
743 | } | |
|
744 | }; | |
|
745 | ||
|
746 | ||
|
747 | /** | |
|
748 | * OnLine presence using channelstream | |
|
749 | */ | |
|
750 | window.ReviewerPresenceController = function (channel) { | |
|
751 | var self = this; | |
|
752 | this.channel = channel; | |
|
753 | this.users = {}; | |
|
754 | ||
|
755 | this.storeUsers = function (users) { | |
|
756 | self.users = {} | |
|
757 | $.each(users, function (index, value) { | |
|
758 | var userId = value.state.id; | |
|
759 | self.users[userId] = value.state; | |
|
760 | }) | |
|
761 | } | |
|
762 | ||
|
763 | this.render = function () { | |
|
764 | $.each($('.reviewer_entry'), function (index, value) { | |
|
765 | var userData = $(value).data(); | |
|
766 | if (self.users[userData.reviewerUserId] !== undefined) { | |
|
767 | $(value).find('.presence-state').show(); | |
|
768 | } else { | |
|
769 | $(value).find('.presence-state').hide(); | |
|
770 | } | |
|
771 | }) | |
|
772 | }; | |
|
773 | ||
|
774 | this.handlePresence = function (data) { | |
|
775 | if (data.type == 'presence' && data.channel === self.channel) { | |
|
776 | this.storeUsers(data.users); | |
|
777 | this.render() | |
|
778 | } | |
|
779 | }; | |
|
780 | ||
|
781 | this.handleChannelUpdate = function (data) { | |
|
782 | if (data.channel === this.channel) { | |
|
783 | this.storeUsers(data.state.users); | |
|
784 | this.render() | |
|
785 | } | |
|
786 | ||
|
787 | }; | |
|
788 | ||
|
789 | /* subscribe to the current presence */ | |
|
790 | $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this)); | |
|
791 | /* subscribe to updates e.g connect/disconnect */ | |
|
792 | $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this)); | |
|
793 | ||
|
794 | }; | |
|
795 | ||
|
796 | window.refreshComments = function (version) { | |
|
797 | version = version || templateContext.pull_request_data.pull_request_version || ''; | |
|
798 | ||
|
799 | // Pull request case | |
|
800 | if (templateContext.pull_request_data.pull_request_id !== null) { | |
|
801 | var params = { | |
|
802 | 'pull_request_id': templateContext.pull_request_data.pull_request_id, | |
|
803 | 'repo_name': templateContext.repo_name, | |
|
804 | 'version': version, | |
|
805 | }; | |
|
806 | var loadUrl = pyroutes.url('pullrequest_comments', params); | |
|
807 | } // commit case | |
|
808 | else { | |
|
809 | return | |
|
810 | } | |
|
811 | ||
|
812 | var currentIDs = [] | |
|
813 | $.each($('.comment'), function (idx, element) { | |
|
814 | currentIDs.push($(element).data('commentId')); | |
|
815 | }); | |
|
816 | var data = {"comments[]": currentIDs}; | |
|
817 | ||
|
818 | var $targetElem = $('.comments-content-table'); | |
|
819 | $targetElem.css('opacity', 0.3); | |
|
820 | $targetElem.load( | |
|
821 | loadUrl, data, function (responseText, textStatus, jqXHR) { | |
|
822 | if (jqXHR.status !== 200) { | |
|
823 | return false; | |
|
824 | } | |
|
825 | var $counterElem = $('#comments-count'); | |
|
826 | var newCount = $(responseText).data('counter'); | |
|
827 | if (newCount !== undefined) { | |
|
828 | var callback = function () { | |
|
829 | $counterElem.animate({'opacity': 1.00}, 200) | |
|
830 | $counterElem.html(newCount); | |
|
831 | }; | |
|
832 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |
|
833 | } | |
|
834 | ||
|
835 | $targetElem.css('opacity', 1); | |
|
836 | tooltipActivate(); | |
|
837 | } | |
|
838 | ); | |
|
839 | } | |
|
840 | ||
|
841 | window.refreshTODOs = function (version) { | |
|
842 | version = version || templateContext.pull_request_data.pull_request_version || ''; | |
|
843 | // Pull request case | |
|
844 | if (templateContext.pull_request_data.pull_request_id !== null) { | |
|
845 | var params = { | |
|
846 | 'pull_request_id': templateContext.pull_request_data.pull_request_id, | |
|
847 | 'repo_name': templateContext.repo_name, | |
|
848 | 'version': version, | |
|
849 | }; | |
|
850 | var loadUrl = pyroutes.url('pullrequest_comments', params); | |
|
851 | } // commit case | |
|
852 | else { | |
|
853 | return | |
|
854 | } | |
|
855 | ||
|
856 | var currentIDs = [] | |
|
857 | $.each($('.comment'), function (idx, element) { | |
|
858 | currentIDs.push($(element).data('commentId')); | |
|
859 | }); | |
|
860 | ||
|
861 | var data = {"comments[]": currentIDs}; | |
|
862 | var $targetElem = $('.todos-content-table'); | |
|
863 | $targetElem.css('opacity', 0.3); | |
|
864 | $targetElem.load( | |
|
865 | loadUrl, data, function (responseText, textStatus, jqXHR) { | |
|
866 | if (jqXHR.status !== 200) { | |
|
867 | return false; | |
|
868 | } | |
|
869 | var $counterElem = $('#todos-count') | |
|
870 | var newCount = $(responseText).data('counter'); | |
|
871 | if (newCount !== undefined) { | |
|
872 | var callback = function () { | |
|
873 | $counterElem.animate({'opacity': 1.00}, 200) | |
|
874 | $counterElem.html(newCount); | |
|
875 | }; | |
|
876 | $counterElem.animate({'opacity': 0.15}, 200, callback); | |
|
877 | } | |
|
878 | ||
|
879 | $targetElem.css('opacity', 1); | |
|
880 | tooltipActivate(); | |
|
881 | } | |
|
882 | ); | |
|
883 | } | |
|
884 | ||
|
885 | window.refreshAllComments = function (version) { | |
|
886 | version = version || templateContext.pull_request_data.pull_request_version || ''; | |
|
887 | ||
|
888 | refreshComments(version); | |
|
889 | refreshTODOs(version); | |
|
890 | }; |
@@ -18,6 +18,7 b'' | |||
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | import io |
|
21 | import math | |
|
21 | 22 | import re |
|
22 | 23 | import os |
|
23 | 24 | import datetime |
@@ -196,6 +197,72 b' def write_metadata_if_needed(event):' | |||
|
196 | 197 | pass |
|
197 | 198 | |
|
198 | 199 | |
|
200 | def write_usage_data(event): | |
|
201 | import rhodecode | |
|
202 | from rhodecode.lib import system_info | |
|
203 | from rhodecode.lib import ext_json | |
|
204 | ||
|
205 | settings = event.app.registry.settings | |
|
206 | instance_tag = settings.get('metadata.write_usage_tag') | |
|
207 | if not settings.get('metadata.write_usage'): | |
|
208 | return | |
|
209 | ||
|
210 | def get_update_age(dest_file): | |
|
211 | now = datetime.datetime.utcnow() | |
|
212 | ||
|
213 | with open(dest_file, 'rb') as f: | |
|
214 | data = ext_json.json.loads(f.read()) | |
|
215 | if 'created_on' in data: | |
|
216 | update_date = parse(data['created_on']) | |
|
217 | diff = now - update_date | |
|
218 | return math.ceil(diff.total_seconds() / 60.0) | |
|
219 | ||
|
220 | return 0 | |
|
221 | ||
|
222 | utc_date = datetime.datetime.utcnow() | |
|
223 | hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.)) | |
|
224 | fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format( | |
|
225 | date=utc_date, hour=hour_quarter) | |
|
226 | ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__')) | |
|
227 | ||
|
228 | usage_dir = os.path.join(ini_loc, '.rcusage') | |
|
229 | if not os.path.isdir(usage_dir): | |
|
230 | os.makedirs(usage_dir) | |
|
231 | usage_metadata_destination = os.path.join(usage_dir, fname) | |
|
232 | ||
|
233 | try: | |
|
234 | age_in_min = get_update_age(usage_metadata_destination) | |
|
235 | except Exception: | |
|
236 | age_in_min = 0 | |
|
237 | ||
|
238 | # write every 6th hour | |
|
239 | if age_in_min and age_in_min < 60 * 6: | |
|
240 | log.debug('Usage file created %s minutes ago, skipping (threashold: %s)...', | |
|
241 | age_in_min, 60 * 6) | |
|
242 | return | |
|
243 | ||
|
244 | def write(dest_file): | |
|
245 | configuration = system_info.SysInfo(system_info.rhodecode_config)()['value'] | |
|
246 | license_token = configuration['config']['license_token'] | |
|
247 | ||
|
248 | metadata = dict( | |
|
249 | desc='Usage data', | |
|
250 | instance_tag=instance_tag, | |
|
251 | license_token=license_token, | |
|
252 | created_on=datetime.datetime.utcnow().isoformat(), | |
|
253 | usage=system_info.SysInfo(system_info.usage_info)()['value'], | |
|
254 | ) | |
|
255 | ||
|
256 | with open(dest_file, 'wb') as f: | |
|
257 | f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True)) | |
|
258 | ||
|
259 | try: | |
|
260 | log.debug('Writing usage file at: %s', usage_metadata_destination) | |
|
261 | write(usage_metadata_destination) | |
|
262 | except Exception: | |
|
263 | pass | |
|
264 | ||
|
265 | ||
|
199 | 266 | def write_js_routes_if_enabled(event): |
|
200 | 267 | registry = event.app.registry |
|
201 | 268 |
@@ -38,10 +38,12 b'' | |||
|
38 | 38 | <div class="main"> |
|
39 | 39 | ${next.main()} |
|
40 | 40 | </div> |
|
41 | ||
|
41 | 42 | </div> |
|
42 | 43 | <!-- END CONTENT --> |
|
43 | 44 | |
|
44 | 45 | </div> |
|
46 | ||
|
45 | 47 | <!-- FOOTER --> |
|
46 | 48 | <div id="footer"> |
|
47 | 49 | <div id="footer-inner" class="title wrapper"> |
@@ -699,9 +701,6 b'' | |||
|
699 | 701 | notice_messages, notice_level = c.rhodecode_user.get_notice_messages() |
|
700 | 702 | notice_display = 'none' if len(notice_messages) == 0 else '' |
|
701 | 703 | %> |
|
702 | <style> | |
|
703 | ||
|
704 | </style> | |
|
705 | 704 | |
|
706 | 705 | <ul id="quick" class="main_nav navigation horizontal-list"> |
|
707 | 706 | ## notice box for important system messages |
@@ -1200,6 +1199,7 b'' | |||
|
1200 | 1199 | ('g p', 'Goto pull requests page'), |
|
1201 | 1200 | ('g o', 'Goto repository settings'), |
|
1202 | 1201 | ('g O', 'Goto repository access permissions settings'), |
|
1202 | ('t s', 'Toggle sidebar on some pages'), | |
|
1203 | 1203 | ] |
|
1204 | 1204 | %> |
|
1205 | 1205 | %for key, desc in elems: |
@@ -1219,3 +1219,36 b'' | |||
|
1219 | 1219 | </div><!-- /.modal-content --> |
|
1220 | 1220 | </div><!-- /.modal-dialog --> |
|
1221 | 1221 | </div><!-- /.modal --> |
|
1222 | ||
|
1223 | ||
|
1224 | <script type="text/javascript"> | |
|
1225 | (function () { | |
|
1226 | "use sctrict"; | |
|
1227 | ||
|
1228 | var $sideBar = $('.right-sidebar'); | |
|
1229 | var expanded = $sideBar.hasClass('right-sidebar-expanded'); | |
|
1230 | var sidebarState = templateContext.session_attrs.sidebarState; | |
|
1231 | var sidebarEnabled = $('aside.right-sidebar').get(0); | |
|
1232 | ||
|
1233 | if (sidebarState === 'expanded') { | |
|
1234 | expanded = true | |
|
1235 | } else if (sidebarState === 'collapsed') { | |
|
1236 | expanded = false | |
|
1237 | } | |
|
1238 | if (sidebarEnabled) { | |
|
1239 | // show sidebar since it's hidden on load | |
|
1240 | $('.right-sidebar').show(); | |
|
1241 | ||
|
1242 | // init based on set initial class, or if defined user session attrs | |
|
1243 | if (expanded) { | |
|
1244 | window.expandSidebar(); | |
|
1245 | window.updateStickyHeader(); | |
|
1246 | ||
|
1247 | } else { | |
|
1248 | window.collapseSidebar(); | |
|
1249 | window.updateStickyHeader(); | |
|
1250 | } | |
|
1251 | } | |
|
1252 | })() | |
|
1253 | ||
|
1254 | </script> |
@@ -4,6 +4,8 b'' | |||
|
4 | 4 | <%namespace name="base" file="/base/base.mako"/> |
|
5 | 5 | <%namespace name="diff_block" file="/changeset/diff_block.mako"/> |
|
6 | 6 | <%namespace name="file_base" file="/files/base.mako"/> |
|
7 | <%namespace name="sidebar" file="/base/sidebar.mako"/> | |
|
8 | ||
|
7 | 9 | |
|
8 | 10 | <%def name="title()"> |
|
9 | 11 | ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)} |
@@ -100,22 +102,6 b'' | |||
|
100 | 102 | % endif |
|
101 | 103 | </div> |
|
102 | 104 |
|
|
103 | %if c.statuses: | |
|
104 | <div class="tag status-tag-${c.statuses[0]} pull-right"> | |
|
105 | <i class="icon-circle review-status-${c.statuses[0]}"></i> | |
|
106 | <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div> | |
|
107 | </div> | |
|
108 | %endif | |
|
109 | ||
|
110 | </div> | |
|
111 | ||
|
112 | </div> | |
|
113 | </div> | |
|
114 | ||
|
115 | <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;"> | |
|
116 | <div class="left-label-summary"> | |
|
117 | <p>${_('Commit navigation')}:</p> | |
|
118 | <div class="right-label-summary"> | |
|
119 | 105 | <span id="parent_link" class="tag tagtag"> |
|
120 | 106 | <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a> |
|
121 | 107 | </span> |
@@ -123,7 +109,9 b'' | |||
|
123 | 109 | <span id="child_link" class="tag tagtag"> |
|
124 | 110 | <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a> |
|
125 | 111 | </span> |
|
112 | ||
|
126 | 113 | </div> |
|
114 | ||
|
127 | 115 | </div> |
|
128 | 116 | </div> |
|
129 | 117 | |
@@ -160,7 +148,9 b'' | |||
|
160 | 148 | <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/> |
|
161 | 149 | ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)} |
|
162 | 150 | ${cbdiffs.render_diffset( |
|
163 |
c.changes[c.commit.raw_id], commit=c.commit, use_comments=True, |
|
|
151 | c.changes[c.commit.raw_id], commit=c.commit, use_comments=True, | |
|
152 | inline_comments=c.inline_comments, | |
|
153 | show_todos=False)} | |
|
164 | 154 | </div> |
|
165 | 155 | |
|
166 | 156 | ## template for inline comment form |
@@ -169,7 +159,7 b'' | |||
|
169 | 159 | ## comments heading with count |
|
170 | 160 | <div class="comments-heading"> |
|
171 | 161 | <i class="icon-comment"></i> |
|
172 | ${_('Comments')} ${len(c.comments)} | |
|
162 | ${_('General Comments')} ${len(c.comments)} | |
|
173 | 163 | </div> |
|
174 | 164 | |
|
175 | 165 | ## render comments |
@@ -180,12 +170,130 b'' | |||
|
180 | 170 | h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))} |
|
181 | 171 | </div> |
|
182 | 172 | |
|
173 | ### NAV SIDEBAR | |
|
174 | <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none"> | |
|
175 | <div class="sidenav navbar__inner" > | |
|
176 | ## TOGGLE | |
|
177 | <div class="sidebar-toggle" onclick="toggleSidebar(); return false"> | |
|
178 | <a href="#toggleSidebar" class="grey-link-action"> | |
|
179 | ||
|
180 | </a> | |
|
181 | </div> | |
|
182 | ||
|
183 | ## CONTENT | |
|
184 | <div class="sidebar-content"> | |
|
185 | ||
|
186 | ## RULES SUMMARY/RULES | |
|
187 | <div class="sidebar-element clear-both"> | |
|
188 | <% vote_title = _ungettext( | |
|
189 | 'Status calculated based on votes from {} reviewer', | |
|
190 | 'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers)) | |
|
191 | %> | |
|
192 | ||
|
193 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> | |
|
194 | <i class="icon-circle review-status-${c.commit_review_status}"></i> | |
|
195 | ${len(c.allowed_reviewers)} | |
|
196 | </div> | |
|
197 | </div> | |
|
198 | ||
|
199 | ## REVIEWERS | |
|
200 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
201 | <span class="tooltip sidebar-heading" title="${vote_title}"> | |
|
202 | <i class="icon-circle review-status-${c.commit_review_status}"></i> | |
|
203 | ${_('Reviewers')} | |
|
204 | </span> | |
|
205 | </div> | |
|
206 | ||
|
207 | <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers"> | |
|
208 | ||
|
209 | <table id="review_members" class="group_members"> | |
|
210 | ## This content is loaded via JS and ReviewersPanel | |
|
211 | </table> | |
|
212 | ||
|
213 | </div> | |
|
214 | ||
|
215 | ## TODOs | |
|
216 | <div class="sidebar-element clear-both"> | |
|
217 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs"> | |
|
218 | <i class="icon-flag-filled"></i> | |
|
219 | <span id="todos-count">${len(c.unresolved_comments)}</span> | |
|
220 | </div> | |
|
221 | ||
|
222 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
223 | ## Only show unresolved, that is only what matters | |
|
224 | <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false"> | |
|
225 | <i class="icon-flag-filled"></i> | |
|
226 | TODOs | |
|
227 | </span> | |
|
228 | ||
|
229 | % if c.resolved_comments: | |
|
230 | <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span> | |
|
231 | % else: | |
|
232 | <span class="block-right last-item noselect">Show resolved</span> | |
|
233 | % endif | |
|
234 | ||
|
235 | </div> | |
|
236 | ||
|
237 | <div class="right-sidebar-expanded-state pr-details-content"> | |
|
238 | % if c.unresolved_comments + c.resolved_comments: | |
|
239 | ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)} | |
|
240 | % else: | |
|
241 | <table> | |
|
242 | <tr> | |
|
243 | <td> | |
|
244 | ${_('No TODOs yet')} | |
|
245 | </td> | |
|
246 | </tr> | |
|
247 | </table> | |
|
248 | % endif | |
|
249 | </div> | |
|
250 | </div> | |
|
251 | ||
|
252 | ## COMMENTS | |
|
253 | <div class="sidebar-element clear-both"> | |
|
254 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}"> | |
|
255 | <i class="icon-comment" style="color: #949494"></i> | |
|
256 | <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span> | |
|
257 | <span class="display-none" id="general-comments-count">${len(c.comments)}</span> | |
|
258 | <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span> | |
|
259 | </div> | |
|
260 | ||
|
261 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
262 | <span class="sidebar-heading noselect" onclick="refreshComments(); return false"> | |
|
263 | <i class="icon-comment" style="color: #949494"></i> | |
|
264 | ${_('Comments')} | |
|
265 | </span> | |
|
266 | ||
|
267 | </div> | |
|
268 | ||
|
269 | <div class="right-sidebar-expanded-state pr-details-content"> | |
|
270 | % if c.inline_comments_flat + c.comments: | |
|
271 | ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)} | |
|
272 | % else: | |
|
273 | <table> | |
|
274 | <tr> | |
|
275 | <td> | |
|
276 | ${_('No Comments yet')} | |
|
277 | </td> | |
|
278 | </tr> | |
|
279 | </table> | |
|
280 | % endif | |
|
281 | </div> | |
|
282 | ||
|
283 | </div> | |
|
284 | ||
|
285 | </div> | |
|
286 | ||
|
287 | </div> | |
|
288 | </aside> | |
|
289 | ||
|
183 | 290 |
|
|
184 | 291 |
|
|
292 | window.setReviewersData = ${c.commit_set_reviewers_data_json | n}; | |
|
185 | 293 | |
|
186 | 294 |
|
|
295 | var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10); | |
|
187 | 296 | |
|
188 | var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10); | |
|
189 | 297 |
|
|
190 | 298 |
|
|
191 | 299 |
|
@@ -225,9 +333,11 b'' | |||
|
225 | 333 | } |
|
226 | 334 | if(data.results.length === 1){ |
|
227 | 335 | var commit = data.results[0]; |
|
228 |
window.location = pyroutes.url('repo_commit', { |
|
|
229 | } | |
|
230 | else if(data.results.length === 2){ | |
|
336 | window.location = pyroutes.url('repo_commit', { | |
|
337 | 'repo_name': '${c.repo_name}', | |
|
338 | 'commit_id': commit.raw_id | |
|
339 | }); | |
|
340 | } else if (data.results.length === 2) { | |
|
231 | 341 | $('#child_link').addClass('disabled'); |
|
232 | 342 | $('#child_link').addClass('double'); |
|
233 | 343 | |
@@ -236,13 +346,19 b'' | |||
|
236 | 346 | .replace('__branch__', data.results[0].branch) |
|
237 | 347 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
238 | 348 | .replace('__title__', data.results[0].message) |
|
239 |
.replace('__url__', pyroutes.url('repo_commit', { |
|
|
349 | .replace('__url__', pyroutes.url('repo_commit', { | |
|
350 | 'repo_name': '${c.repo_name}', | |
|
351 | 'commit_id': data.results[0].raw_id | |
|
352 | })); | |
|
240 | 353 | _html +=' | '; |
|
241 | 354 | _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> ' |
|
242 | 355 | .replace('__branch__', data.results[1].branch) |
|
243 | 356 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
244 | 357 | .replace('__title__', data.results[1].message) |
|
245 |
.replace('__url__', pyroutes.url('repo_commit', { |
|
|
358 | .replace('__url__', pyroutes.url('repo_commit', { | |
|
359 | 'repo_name': '${c.repo_name}', | |
|
360 | 'commit_id': data.results[1].raw_id | |
|
361 | })); | |
|
246 | 362 | $('#child_link').html(_html); |
|
247 | 363 | } |
|
248 | 364 | } |
@@ -264,9 +380,11 b'' | |||
|
264 | 380 | } |
|
265 | 381 | if(data.results.length === 1){ |
|
266 | 382 | var commit = data.results[0]; |
|
267 |
window.location = pyroutes.url('repo_commit', { |
|
|
268 | } | |
|
269 | else if(data.results.length === 2){ | |
|
383 | window.location = pyroutes.url('repo_commit', { | |
|
384 | 'repo_name': '${c.repo_name}', | |
|
385 | 'commit_id': commit.raw_id | |
|
386 | }); | |
|
387 | } else if (data.results.length === 2) { | |
|
270 | 388 | $('#parent_link').addClass('disabled'); |
|
271 | 389 | $('#parent_link').addClass('double'); |
|
272 | 390 | |
@@ -275,13 +393,19 b'' | |||
|
275 | 393 | .replace('__branch__', data.results[0].branch) |
|
276 | 394 | .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6))) |
|
277 | 395 | .replace('__title__', data.results[0].message) |
|
278 |
.replace('__url__', pyroutes.url('repo_commit', { |
|
|
396 | .replace('__url__', pyroutes.url('repo_commit', { | |
|
397 | 'repo_name': '${c.repo_name}', | |
|
398 | 'commit_id': data.results[0].raw_id | |
|
399 | })); | |
|
279 | 400 | _html +=' | '; |
|
280 | 401 | _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>' |
|
281 | 402 | .replace('__branch__', data.results[1].branch) |
|
282 | 403 | .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6))) |
|
283 | 404 | .replace('__title__', data.results[1].message) |
|
284 |
.replace('__url__', pyroutes.url('repo_commit', { |
|
|
405 | .replace('__url__', pyroutes.url('repo_commit', { | |
|
406 | 'repo_name': '${c.repo_name}', | |
|
407 | 'commit_id': data.results[1].raw_id | |
|
408 | })); | |
|
285 | 409 | $('#parent_link').html(_html); |
|
286 | 410 | } |
|
287 | 411 | } |
@@ -296,6 +420,11 b'' | |||
|
296 | 420 |
|
|
297 | 421 |
|
|
298 | 422 | |
|
423 | ReviewersPanel.init(null, setReviewersData); | |
|
424 | ||
|
425 | var channel = '${c.commit_broadcast_channel}'; | |
|
426 | new ReviewerPresenceController(channel) | |
|
427 | ||
|
299 | 428 |
|
|
300 | 429 |
|
|
301 | 430 |
@@ -10,12 +10,18 b'' | |||
|
10 | 10 | |
|
11 | 11 | <%namespace name="base" file="/base/base.mako"/> |
|
12 | 12 | <%def name="comment_block(comment, inline=False, active_pattern_entries=None)"> |
|
13 | <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %> | |
|
13 | ||
|
14 | <% | |
|
15 | from rhodecode.model.comment import CommentsModel | |
|
16 | comment_model = CommentsModel() | |
|
17 | %> | |
|
18 | <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %> | |
|
14 | 19 | <% latest_ver = len(getattr(c, 'versions', [])) %> |
|
20 | ||
|
15 | 21 | % if inline: |
|
16 |
<% outdated_at_ver = comment.outdated_at_version( |
|
|
22 | <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %> | |
|
17 | 23 | % else: |
|
18 |
<% outdated_at_ver = comment.older_than_version( |
|
|
24 | <% outdated_at_ver = comment.older_than_version(c.at_version_num) %> | |
|
19 | 25 | % endif |
|
20 | 26 | |
|
21 | 27 | <div class="comment |
@@ -70,9 +76,9 b'' | |||
|
70 | 76 | status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id)) |
|
71 | 77 | %> |
|
72 | 78 | |
|
73 |
<i class="icon-circle review-status-${comment. |
|
|
79 | <i class="icon-circle review-status-${comment.review_status}"></i> | |
|
74 | 80 | <div class="changeset-status-lbl tooltip" title="${status_change_title}"> |
|
75 |
${comment. |
|
|
81 | ${comment.review_status_lbl} | |
|
76 | 82 | </div> |
|
77 | 83 | % else: |
|
78 | 84 | <div> |
@@ -153,69 +159,90 b'' | |||
|
153 | 159 | </div> |
|
154 | 160 | %endif |
|
155 | 161 | |
|
156 | <a class="permalink" href="#comment-${comment.comment_id}"> ¶</a> | |
|
157 | ||
|
158 | 162 | <div class="comment-links-block"> |
|
159 | 163 | |
|
160 | 164 | % if inline: |
|
161 | 165 | <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}"> |
|
162 | 166 | % if outdated_at_ver: |
|
163 |
<code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format( |
|
|
164 | outdated ${'v{}'.format(pr_index_ver)} | | |
|
165 |
|
|
|
166 | % elif pr_index_ver: | |
|
167 | <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"> | |
|
168 | ${'v{}'.format(pr_index_ver)} | | |
|
169 | </code> | |
|
167 | <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">outdated ${'v{}'.format(comment_ver)}</code> | |
|
168 | <code class="action-divider">|</code> | |
|
169 | % elif comment_ver: | |
|
170 | <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code> | |
|
171 | <code class="action-divider">|</code> | |
|
170 | 172 | % endif |
|
171 | 173 | </a> |
|
172 | 174 | % else: |
|
173 |
% if |
|
|
175 | % if comment_ver: | |
|
174 | 176 | |
|
175 | 177 | % if comment.outdated: |
|
176 | 178 | <a class="pr-version" |
|
177 | 179 | href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}" |
|
178 | 180 | > |
|
179 |
${_('Outdated comment from pull request version v{0}, latest v{1}').format( |
|
|
180 |
</a> |
|
|
181 | ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)} | |
|
182 | </a> | |
|
183 | <code class="action-divider">|</code> | |
|
181 | 184 | % else: |
|
182 | 185 | <a class="tooltip pr-version" |
|
183 |
title="${_('Comment from pull request version v{0}, latest v{1}').format( |
|
|
186 | title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}" | |
|
184 | 187 | href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}" |
|
185 | 188 | > |
|
186 | <code class="pr-version-num"> | |
|
187 | ${'v{}'.format(pr_index_ver)} | |
|
188 | </code> | |
|
189 | </a> | | |
|
189 | <code class="pr-version-num">${'v{}'.format(comment_ver)}</code> | |
|
190 | </a> | |
|
191 | <code class="action-divider">|</code> | |
|
190 | 192 | % endif |
|
191 | 193 | |
|
192 | 194 | % endif |
|
193 | 195 | % endif |
|
194 | 196 |
|
|
197 | <details class="details-reset details-inline-block"> | |
|
198 | <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary> | |
|
199 | <details-menu class="details-dropdown"> | |
|
200 | ||
|
201 | <div class="dropdown-item"> | |
|
202 | ${_('Comment')} #${comment.comment_id} | |
|
203 | <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span> | |
|
204 | </div> | |
|
205 | ||
|
195 | 206 | ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed |
|
196 | 207 | ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated |
|
197 | 208 | %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())): |
|
198 | 209 | ## permissions to delete |
|
199 | 210 | %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id): |
|
200 | <a onclick="return Rhodecode.comments.editComment(this);" | |
|
201 |
|
|
|
202 |
|
|
|
203 | class="delete-comment">${_('Delete')}</a> | |
|
211 | <div class="dropdown-divider"></div> | |
|
212 | <div class="dropdown-item"> | |
|
213 | <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a> | |
|
214 | </div> | |
|
215 | <div class="dropdown-item"> | |
|
216 | <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a> | |
|
217 | </div> | |
|
204 | 218 | %else: |
|
219 | <div class="dropdown-divider"></div> | |
|
220 | <div class="dropdown-item"> | |
|
205 | 221 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a> |
|
206 | | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a> | |
|
222 | </div> | |
|
223 | <div class="dropdown-item"> | |
|
224 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a> | |
|
225 | </div> | |
|
207 | 226 | %endif |
|
208 | 227 | %else: |
|
228 | <div class="dropdown-divider"></div> | |
|
229 | <div class="dropdown-item"> | |
|
209 | 230 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a> |
|
210 | | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a> | |
|
231 | </div> | |
|
232 | <div class="dropdown-item"> | |
|
233 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a> | |
|
234 | </div> | |
|
211 | 235 | %endif |
|
236 | </details-menu> | |
|
237 | </details> | |
|
212 | 238 |
|
|
239 | <code class="action-divider">|</code> | |
|
213 | 240 | % if outdated_at_ver: |
|
214 |
|
|
|
215 |
|
|
|
241 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a> | |
|
242 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a> | |
|
216 | 243 | % else: |
|
217 |
|
|
|
218 |
|
|
|
244 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a> | |
|
245 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a> | |
|
219 | 246 | % endif |
|
220 | 247 | |
|
221 | 248 | </div> |
@@ -102,6 +102,11 b'' | |||
|
102 | 102 | <%namespace name="diff_block" file="/changeset/diff_block.mako"/> |
|
103 | 103 | |
|
104 | 104 | %for commit in c.commit_ranges: |
|
105 | ## commit range header for each individual diff | |
|
106 | <h3> | |
|
107 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a> | |
|
108 | </h3> | |
|
109 | ||
|
105 | 110 | ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])} |
|
106 | 111 | ${cbdiffs.render_diffset( |
|
107 | 112 | diffset=c.changes[commit.raw_id], |
@@ -61,6 +61,8 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
61 | 61 | diffset_container_id = h.md5(diffset.target_ref) |
|
62 | 62 | collapse_all = len(diffset.files) > collapse_when_files_over |
|
63 | 63 | active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None)) |
|
64 | from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \ | |
|
65 | MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE | |
|
64 | 66 | %> |
|
65 | 67 | |
|
66 | 68 | %if use_comments: |
@@ -159,45 +161,45 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
159 | 161 | </div> |
|
160 | 162 | % endif |
|
161 | 163 | |
|
162 |
|
|
|
163 | <div class="pull-right"> | |
|
164 |
|
|
|
165 | % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'): | |
|
166 |
|
|
|
167 | % if c.comments: | |
|
168 | <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>, | |
|
169 | % else: | |
|
170 |
|
|
|
171 |
|
|
|
172 | ||
|
173 |
|
|
|
174 |
|
|
|
175 | id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)} | |
|
176 |
|
|
|
177 | % else: | |
|
178 |
|
|
|
179 |
|
|
|
180 | % endif | |
|
181 | ||
|
182 |
|
|
|
183 |
|
|
|
184 | outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver'] | |
|
185 | %> | |
|
186 | ||
|
187 |
|
|
|
188 |
|
|
|
189 | (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
190 |
|
|
|
191 |
|
|
|
192 |
|
|
|
193 | % else: | |
|
194 | (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
195 | % endif | |
|
196 | ||
|
197 |
|
|
|
198 | ||
|
199 |
|
|
|
200 |
|
|
|
164 | ## ## comments | |
|
165 | ## <div class="pull-right"> | |
|
166 | ## <div class="comments-number" style="padding-left: 10px"> | |
|
167 | ## % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'): | |
|
168 | ## <i class="icon-comment" style="color: #949494">COMMENTS:</i> | |
|
169 | ## % if c.comments: | |
|
170 | ## <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>, | |
|
171 | ## % else: | |
|
172 | ## ${_('0 General')} | |
|
173 | ## % endif | |
|
174 | ## | |
|
175 | ## % if c.inline_cnt: | |
|
176 | ## <a href="#" onclick="return Rhodecode.comments.nextComment();" | |
|
177 | ## id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)} | |
|
178 | ## </a> | |
|
179 | ## % else: | |
|
180 | ## ${_('0 Inline')} | |
|
181 | ## % endif | |
|
182 | ## % endif | |
|
183 | ## | |
|
184 | ## % if pull_request_menu: | |
|
185 | ## <% | |
|
186 | ## outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver'] | |
|
187 | ## %> | |
|
188 | ## | |
|
189 | ## % if outdated_comm_count_ver: | |
|
190 | ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;"> | |
|
191 | ## (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
192 | ## </a> | |
|
193 | ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a> | |
|
194 | ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a> | |
|
195 | ## % else: | |
|
196 | ## (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
197 | ## % endif | |
|
198 | ## | |
|
199 | ## % endif | |
|
200 | ## | |
|
201 | ## </div> | |
|
202 | ## </div> | |
|
201 | 203 | |
|
202 | 204 | </div> |
|
203 | 205 | |
@@ -208,13 +210,6 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
208 | 210 | <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a> |
|
209 | 211 | </h2> |
|
210 | 212 | </div> |
|
211 | ## commit range header for each individual diff | |
|
212 | % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1: | |
|
213 | <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}"> | |
|
214 | <div class="clearinner"> | |
|
215 | <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a> | |
|
216 | </div> | |
|
217 | </div> | |
|
218 | 213 | % endif |
|
219 | 214 | |
|
220 | 215 | <div id="todo-box"> |
@@ -239,6 +234,43 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
239 | 234 | <% over_lines_changed_limit = False %> |
|
240 | 235 | %for i, filediff in enumerate(diffset.files): |
|
241 | 236 | |
|
237 | %if filediff.source_file_path and filediff.target_file_path: | |
|
238 | %if filediff.source_file_path != filediff.target_file_path: | |
|
239 | ## file was renamed, or copied | |
|
240 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: | |
|
241 | <% | |
|
242 | final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> <del>{}</del>'.format(filediff.target_file_path, filediff.source_file_path)) | |
|
243 | final_path = filediff.target_file_path | |
|
244 | %> | |
|
245 | %elif COPIED_FILENODE in filediff.patch['stats']['ops']: | |
|
246 | <% | |
|
247 | final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> {}'.format(filediff.target_file_path, filediff.source_file_path)) | |
|
248 | final_path = filediff.target_file_path | |
|
249 | %> | |
|
250 | %endif | |
|
251 | %else: | |
|
252 | ## file was modified | |
|
253 | <% | |
|
254 | final_file_name = filediff.source_file_path | |
|
255 | final_path = final_file_name | |
|
256 | %> | |
|
257 | %endif | |
|
258 | %else: | |
|
259 | %if filediff.source_file_path: | |
|
260 | ## file was deleted | |
|
261 | <% | |
|
262 | final_file_name = filediff.source_file_path | |
|
263 | final_path = final_file_name | |
|
264 | %> | |
|
265 | %else: | |
|
266 | ## file was added | |
|
267 | <% | |
|
268 | final_file_name = filediff.target_file_path | |
|
269 | final_path = final_file_name | |
|
270 | %> | |
|
271 | %endif | |
|
272 | %endif | |
|
273 | ||
|
242 | 274 | <% |
|
243 | 275 | lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted'] |
|
244 | 276 | over_lines_changed_limit = lines_changed > lines_changed_limit |
@@ -258,13 +290,39 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
258 | 290 | total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated] |
|
259 | 291 | %> |
|
260 | 292 | <div class="filediff-collapse-indicator icon-"></div> |
|
293 | ||
|
294 | ## Comments/Options PILL | |
|
261 | 295 |
<span class="pill-group pull-right" |
|
262 | 296 | <span class="pill" op="comments"> |
|
263 | ||
|
264 | 297 | <i class="icon-comment"></i> ${len(total_file_comments)} |
|
265 | 298 | </span> |
|
299 | ||
|
300 | <details class="details-reset details-inline-block"> | |
|
301 | <summary class="noselect"> | |
|
302 | <i class="pill icon-options cursor-pointer" op="options"></i> | |
|
303 | </summary> | |
|
304 | <details-menu class="details-dropdown"> | |
|
305 | ||
|
306 | <div class="dropdown-item"> | |
|
307 | <span>${final_path}</span> | |
|
308 | <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="Copy file path"></span> | |
|
309 | </div> | |
|
310 | ||
|
311 | <div class="dropdown-divider"></div> | |
|
312 | ||
|
313 | <div class="dropdown-item"> | |
|
314 | <% permalink = request.current_route_url(_anchor='a_{}'.format(h.FID(filediff.raw_id, filediff.patch['filename']))) %> | |
|
315 | <a href="${permalink}">ΒΆ permalink</a> | |
|
316 | <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${permalink}" title="Copy permalink"></span> | |
|
317 | </div> | |
|
318 | ||
|
319 | ||
|
320 | </details-menu> | |
|
321 | </details> | |
|
322 | ||
|
266 | 323 | </span> |
|
267 | ${diff_ops(filediff)} | |
|
324 | ||
|
325 | ${diff_ops(final_file_name, filediff)} | |
|
268 | 326 | |
|
269 | 327 | </label> |
|
270 | 328 | |
@@ -463,43 +521,15 b" return '%s_%s_%i' % (h.md5_safe(commit+f" | |||
|
463 | 521 | </div> |
|
464 | 522 | </%def> |
|
465 | 523 | |
|
466 | <%def name="diff_ops(filediff)"> | |
|
524 | <%def name="diff_ops(file_name, filediff)"> | |
|
467 | 525 | <% |
|
468 | 526 | from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \ |
|
469 | 527 | MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE |
|
470 | 528 | %> |
|
471 | 529 | <span class="pill"> |
|
472 | 530 | <i class="icon-file-text"></i> |
|
473 | %if filediff.source_file_path and filediff.target_file_path: | |
|
474 | %if filediff.source_file_path != filediff.target_file_path: | |
|
475 | ## file was renamed, or copied | |
|
476 | %if RENAMED_FILENODE in filediff.patch['stats']['ops']: | |
|
477 | ${filediff.target_file_path} β¬ <del>${filediff.source_file_path}</del> | |
|
478 | <% final_path = filediff.target_file_path %> | |
|
479 | %elif COPIED_FILENODE in filediff.patch['stats']['ops']: | |
|
480 | ${filediff.target_file_path} β¬ ${filediff.source_file_path} | |
|
481 | <% final_path = filediff.target_file_path %> | |
|
482 | %endif | |
|
483 | %else: | |
|
484 | ## file was modified | |
|
485 | ${filediff.source_file_path} | |
|
486 | <% final_path = filediff.source_file_path %> | |
|
487 | %endif | |
|
488 | %else: | |
|
489 | %if filediff.source_file_path: | |
|
490 | ## file was deleted | |
|
491 | ${filediff.source_file_path} | |
|
492 | <% final_path = filediff.source_file_path %> | |
|
493 | %else: | |
|
494 | ## file was added | |
|
495 | ${filediff.target_file_path} | |
|
496 | <% final_path = filediff.target_file_path %> | |
|
497 | %endif | |
|
498 | %endif | |
|
499 | <i style="color: #aaa" class="on-hover-icon icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy file path')}" onclick="return false;"></i> | |
|
531 | ${file_name} | |
|
500 | 532 | </span> |
|
501 | ## anchor link | |
|
502 | <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a> | |
|
503 | 533 | |
|
504 | 534 | <span class="pill-group pull-right"> |
|
505 | 535 | |
@@ -934,7 +964,7 b' def get_comments_for(diff_type, comments' | |||
|
934 | 964 | </span> |
|
935 | 965 | %endif |
|
936 | 966 | % if commit or pull_request_menu: |
|
937 | <span id="diff_nav">Loading diff...:</span> | |
|
967 | <span class="tooltip" title="Navigate to previous or next change inside files." id="diff_nav">Loading diff...:</span> | |
|
938 | 968 | <span class="cursor-pointer" onclick="scrollToPrevChunk(); return false"> |
|
939 | 969 | <i class="icon-angle-up"></i> |
|
940 | 970 | </span> |
@@ -21,8 +21,9 b'' | |||
|
21 | 21 | ## to speed up lookups cache some functions before the loop |
|
22 | 22 | <% |
|
23 | 23 | active_patterns = h.get_active_pattern_entries(c.repo_name) |
|
24 | urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns) | |
|
24 | urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns, issues_container=getattr(c, 'referenced_commit_issues', None)) | |
|
25 | 25 | %> |
|
26 | ||
|
26 | 27 | %for commit in c.commit_ranges: |
|
27 | 28 | <tr id="row-${commit.raw_id}" |
|
28 | 29 | commit_id="${commit.raw_id}" |
@@ -1,6 +1,10 b'' | |||
|
1 | 1 | <%text> |
|
2 | 2 | <div style="display: none"> |
|
3 | 3 | |
|
4 | <script> | |
|
5 | var CG = new ColorGenerator(); | |
|
6 | </script> | |
|
7 | ||
|
4 | 8 | <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate"> |
|
5 | 9 | |
|
6 | 10 | <% |
@@ -34,13 +38,7 b" var data_hovercard_url = pyroutes.url('h" | |||
|
34 | 38 | |
|
35 | 39 | </script> |
|
36 | 40 | |
|
37 | <script> | |
|
38 | var CG = new ColorGenerator(); | |
|
39 | </script> | |
|
40 | ||
|
41 | 41 | <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate"> |
|
42 | ||
|
43 | <li id="reviewer_<%= member.user_id %>" class="reviewer_entry"> | |
|
44 | 42 |
|
|
45 | 43 |
|
|
46 | 44 |
|
@@ -49,23 +47,32 b' var CG = new ColorGenerator();' | |||
|
49 | 47 |
|
|
50 | 48 | |
|
51 | 49 |
|
|
52 | var groupStyle = 'border-left: 1px solid '+CG.asRGB(CG.getColor(member.user_group.vote_rule)); | |
|
50 | var reviewGroup = '<i class="icon-user-group"></i>'; | |
|
51 | var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule)); | |
|
53 | 52 |
|
|
54 | var groupStyle = 'border-left: 1px solid white'; | |
|
53 | var reviewGroup = null; | |
|
54 | var reviewGroupColor = 'transparent'; | |
|
55 | 55 |
|
|
56 | var rule_show = rule_show || false; | |
|
57 | ||
|
58 | if (rule_show) { | |
|
59 | var rule_visibility = 'table-cell'; | |
|
60 | } else { | |
|
61 | var rule_visibility = 'none'; | |
|
62 | } | |
|
63 | ||
|
56 | 64 |
|
|
57 | 65 | |
|
58 | <div class="reviewers_member" style="<%= groupStyle%>" > | |
|
66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> | |
|
67 | ||
|
68 | <td style="width: 20px"> | |
|
59 | 69 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> |
|
60 | 70 | <i class="icon-circle review-status-<%= review_status %>"></i> |
|
61 | 71 | </div> |
|
72 | </td> | |
|
73 | ||
|
74 | <td> | |
|
62 | 75 |
|
|
63 | <% if (mandatory) { %> | |
|
64 | <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer"> | |
|
65 | <i class="icon-lock"></i> | |
|
66 | </div> | |
|
67 | <% } %> | |
|
68 | ||
|
69 | 76 | <%- |
|
70 | 77 | renderTemplate('gravatarWithUser', { |
|
71 | 78 | 'size': 16, |
@@ -77,11 +84,43 b' var CG = new ColorGenerator();' | |||
|
77 | 84 | 'gravatar_url': member.gravatar_link |
|
78 | 85 | }) |
|
79 | 86 | %> |
|
87 | <span class="tooltip presence-state" style="display: none" title="This users is currently at this page"> | |
|
88 | <i class="icon-eye" style="color: #0ac878"></i> | |
|
89 | </span> | |
|
80 | 90 | </div> |
|
91 | </td> | |
|
92 | ||
|
93 | <td style="width: 10px"> | |
|
94 | <% if (reviewGroup !== null) { %> | |
|
95 | <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>"> | |
|
96 | <%- reviewGroup %> | |
|
97 | </span> | |
|
98 | <% } %> | |
|
99 | </td> | |
|
81 | 100 | |
|
101 | <% if (mandatory) { %> | |
|
102 | <td style="text-align: right;width: 10px;"> | |
|
103 | <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer"> | |
|
104 | <i class="icon-lock"></i> | |
|
105 | </div> | |
|
106 | </td> | |
|
107 | ||
|
108 | <% } else { %> | |
|
109 | <td style="text-align: right;width: 10px;"> | |
|
110 | <% if (allowed_to_update) { %> | |
|
111 | <div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;"> | |
|
112 | <i class="icon-remove"></i> | |
|
113 | </div> | |
|
114 | <% } %> | |
|
115 | </td> | |
|
116 | <% } %> | |
|
117 | ||
|
118 | </tr> | |
|
119 | ||
|
120 | <tr> | |
|
121 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> | |
|
82 | 122 | <input type="hidden" name="__start__" value="reviewer:mapping"> |
|
83 | 123 | |
|
84 | ||
|
85 | 124 | <%if (member.user_group && member.user_group.vote_rule) {%> |
|
86 | 125 | <div class="reviewer_reason"> |
|
87 | 126 | |
@@ -112,24 +151,11 b' var CG = new ColorGenerator();' | |||
|
112 | 151 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> |
|
113 | 152 | |
|
114 | 153 | <input type="hidden" name="__end__" value="reviewer:mapping"> |
|
115 | ||
|
116 | <% if (mandatory) { %> | |
|
117 | <div class="reviewer_member_mandatory_remove" style="visibility: <%= edit_visibility %>;"> | |
|
118 | <i class="icon-remove"></i> | |
|
119 | </div> | |
|
120 | <% } else { %> | |
|
121 | <% if (allowed_to_update) { %> | |
|
122 | <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;"> | |
|
123 | <i class="icon-remove" ></i> | |
|
124 | </div> | |
|
125 | <% } %> | |
|
126 | <% } %> | |
|
127 | </div> | |
|
128 | </li> | |
|
154 | </td> | |
|
155 | </tr> | |
|
129 | 156 | |
|
130 | 157 | </script> |
|
131 | 158 | |
|
132 | ||
|
133 | 159 | <script id="ejs_commentVersion" type="text/template" class="ejsTemplate"> |
|
134 | 160 | |
|
135 | 161 | <% |
@@ -158,8 +184,56 b' if (show_disabled) {' | |||
|
158 | 184 | </script> |
|
159 | 185 | |
|
160 | 186 | |
|
187 | <script id="ejs_sideBarCommentHovercard" type="text/template" class="ejsTemplate"> | |
|
188 | ||
|
189 | <div> | |
|
190 | <% if (is_todo) { %> | |
|
191 | <% if (inline) { %> | |
|
192 | <strong>Inline</strong> TODO on line: <%= line_no %> | |
|
193 | <% if (version_info) { %> | |
|
194 | <%= version_info %> | |
|
195 | <% } %> | |
|
196 | <br/> | |
|
197 | File: <code><%- file_name -%></code> | |
|
198 | <% } else { %> | |
|
199 | <% if (review_status) { %> | |
|
200 | <i class="icon-circle review-status-<%= review_status %>"></i> | |
|
201 | <% } %> | |
|
202 | <strong>General</strong> TODO | |
|
203 | <% if (version_info) { %> | |
|
204 | <%= version_info %> | |
|
205 | <% } %> | |
|
206 | <% } %> | |
|
207 | <% } else { %> | |
|
208 | <% if (inline) { %> | |
|
209 | <strong>Inline</strong> comment on line: <%= line_no %> | |
|
210 | <% if (version_info) { %> | |
|
211 | <%= version_info %> | |
|
212 | <% } %> | |
|
213 | <br/> | |
|
214 | File: <code><%- file_name -%></code> | |
|
215 | <% } else { %> | |
|
216 | <% if (review_status) { %> | |
|
217 | <i class="icon-circle review-status-<%= review_status %>"></i> | |
|
218 | <% } %> | |
|
219 | <strong>General</strong> comment | |
|
220 | <% if (version_info) { %> | |
|
221 | <%= version_info %> | |
|
222 | <% } %> | |
|
223 | <% } %> | |
|
224 | <% } %> | |
|
225 | <br/> | |
|
226 | Created: | |
|
227 | <time class="timeago" title="<%= created_on %>" datetime="<%= datetime %>"><%= $.timeago(datetime) %></time> | |
|
228 | ||
|
161 | 229 | </div> |
|
162 | 230 | |
|
231 | </script> | |
|
232 | ||
|
233 | ##// END OF EJS Templates | |
|
234 | </div> | |
|
235 | ||
|
236 | ||
|
163 | 237 | <script> |
|
164 | 238 | // registers the templates into global cache |
|
165 | 239 | registerTemplates(); |
@@ -360,13 +360,13 b' text_monospace = "\'Menlo\', \'Liberation M' | |||
|
360 | 360 | |
|
361 | 361 | div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li { |
|
362 | 362 | list-style: none !important; |
|
363 |
margin: |
|
|
363 | margin: 0px !important; | |
|
364 | 364 | padding: 0 !important |
|
365 | 365 | } |
|
366 | 366 | |
|
367 | 367 | div.markdown-block ul li, div.markdown-block ol li { |
|
368 | 368 | list-style: disc !important; |
|
369 |
margin: |
|
|
369 | margin: 0px !important; | |
|
370 | 370 | padding: 0 !important |
|
371 | 371 | } |
|
372 | 372 |
@@ -19,20 +19,73 b'' | |||
|
19 | 19 | <div class="box"> |
|
20 | 20 | ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)} |
|
21 | 21 | |
|
22 |
<div class="box |
|
|
22 | <div class="box"> | |
|
23 | 23 | |
|
24 | 24 | <div class="summary-details block-left"> |
|
25 | 25 | |
|
26 | ||
|
27 | <div class="pr-details-title"> | |
|
28 | ${_('New pull request')} | |
|
29 | </div> | |
|
30 | ||
|
31 | 26 | <div class="form" style="padding-top: 10px"> |
|
32 | <!-- fields --> | |
|
33 | 27 | |
|
34 | 28 | <div class="fields" > |
|
35 | 29 | |
|
30 | ## COMMIT FLOW | |
|
31 | <div class="field"> | |
|
32 | <div class="label label-textarea"> | |
|
33 | <label for="commit_flow">${_('Commit flow')}:</label> | |
|
34 | </div> | |
|
35 | ||
|
36 | <div class="content"> | |
|
37 | <div class="flex-container"> | |
|
38 | <div style="width: 45%;"> | |
|
39 | <div class="panel panel-default source-panel"> | |
|
40 | <div class="panel-heading"> | |
|
41 | <h3 class="panel-title">${_('Source repository')}</h3> | |
|
42 | </div> | |
|
43 | <div class="panel-body"> | |
|
44 | <div style="display:none">${c.rhodecode_db_repo.description}</div> | |
|
45 | ${h.hidden('source_repo')} | |
|
46 | ${h.hidden('source_ref')} | |
|
47 | ||
|
48 | <div id="pr_open_message"></div> | |
|
49 | </div> | |
|
50 | </div> | |
|
51 | </div> | |
|
52 | ||
|
53 | <div style="width: 90px; text-align: center; padding-top: 30px"> | |
|
54 | <div> | |
|
55 | <i class="icon-right" style="font-size: 2.2em"></i> | |
|
56 | </div> | |
|
57 | <div style="position: relative; top: 10px"> | |
|
58 | <span class="tag tag"> | |
|
59 | <span id="switch_base"></span> | |
|
60 | </span> | |
|
61 | </div> | |
|
62 | ||
|
63 | </div> | |
|
64 | ||
|
65 | <div style="width: 45%;"> | |
|
66 | ||
|
67 | <div class="panel panel-default target-panel"> | |
|
68 | <div class="panel-heading"> | |
|
69 | <h3 class="panel-title">${_('Target repository')}</h3> | |
|
70 | </div> | |
|
71 | <div class="panel-body"> | |
|
72 | <div style="display:none" id="target_repo_desc"></div> | |
|
73 | ${h.hidden('target_repo')} | |
|
74 | ${h.hidden('target_ref')} | |
|
75 | <span id="target_ref_loading" style="display: none"> | |
|
76 | ${_('Loading refs...')} | |
|
77 | </span> | |
|
78 | </div> | |
|
79 | </div> | |
|
80 | ||
|
81 | </div> | |
|
82 | </div> | |
|
83 | ||
|
84 | </div> | |
|
85 | ||
|
86 | </div> | |
|
87 | ||
|
88 | ## TITLE | |
|
36 | 89 |
|
|
37 | 90 | <div class="label"> |
|
38 | 91 | <label for="pullrequest_title">${_('Title')}:</label> |
@@ -45,6 +98,7 b'' | |||
|
45 | 98 | </p> |
|
46 | 99 |
|
|
47 | 100 | |
|
101 | ## DESC | |
|
48 | 102 | <div class="field"> |
|
49 | 103 | <div class="label label-textarea"> |
|
50 | 104 | <label for="pullrequest_desc">${_('Description')}:</label> |
@@ -55,72 +109,14 b'' | |||
|
55 | 109 | </div> |
|
56 | 110 | </div> |
|
57 | 111 | |
|
112 | ## REVIEWERS | |
|
58 | 113 | <div class="field"> |
|
59 | 114 | <div class="label label-textarea"> |
|
60 |
<label for=" |
|
|
61 | </div> | |
|
62 | ||
|
63 | ## TODO: johbo: Abusing the "content" class here to get the | |
|
64 | ## desired effect. Should be replaced by a proper solution. | |
|
65 | ||
|
66 | ##ORG | |
|
67 | <div class="content"> | |
|
68 | <strong>${_('Source repository')}:</strong> | |
|
69 | ${c.rhodecode_db_repo.description} | |
|
115 | <label for="pullrequest_reviewers">${_('Reviewers')}:</label> | |
|
70 | 116 | </div> |
|
71 | 117 | <div class="content"> |
|
72 | ${h.hidden('source_repo')} | |
|
73 | ${h.hidden('source_ref')} | |
|
74 | </div> | |
|
75 | ||
|
76 | ##OTHER, most Probably the PARENT OF THIS FORK | |
|
77 | <div class="content"> | |
|
78 | ## filled with JS | |
|
79 | <div id="target_repo_desc"></div> | |
|
80 | </div> | |
|
81 | ||
|
82 | <div class="content"> | |
|
83 | ${h.hidden('target_repo')} | |
|
84 | ${h.hidden('target_ref')} | |
|
85 | <span id="target_ref_loading" style="display: none"> | |
|
86 | ${_('Loading refs...')} | |
|
87 | </span> | |
|
88 | </div> | |
|
89 | </div> | |
|
90 | ||
|
91 | <div class="field"> | |
|
92 | <div class="label label-textarea"> | |
|
93 | <label for="pullrequest_submit"></label> | |
|
94 | </div> | |
|
95 | <div class="input"> | |
|
96 | <div class="pr-submit-button"> | |
|
97 | <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}"> | |
|
98 | </div> | |
|
99 | <div id="pr_open_message"></div> | |
|
100 | </div> | |
|
101 | </div> | |
|
102 | ||
|
103 | <div class="pr-spacing-container"></div> | |
|
104 | </div> | |
|
105 | </div> | |
|
106 | </div> | |
|
107 | <div> | |
|
108 | ## AUTHOR | |
|
109 | <div class="reviewers-title block-right"> | |
|
110 | <div class="pr-details-title"> | |
|
111 | ${_('Author of this pull request')} | |
|
112 | </div> | |
|
113 | </div> | |
|
114 | <div class="block-right pr-details-content reviewers"> | |
|
115 | <ul class="group_members"> | |
|
116 | <li> | |
|
117 | ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)} | |
|
118 | </li> | |
|
119 | </ul> | |
|
120 | </div> | |
|
121 | ||
|
122 | 118 | ## REVIEW RULES |
|
123 |
<div id="review_rules" style="display: none" class="reviewers-title |
|
|
119 | <div id="review_rules" style="display: none" class="reviewers-title"> | |
|
124 | 120 | <div class="pr-details-title"> |
|
125 | 121 | ${_('Reviewer rules')} |
|
126 | 122 | </div> |
@@ -130,32 +126,48 b'' | |||
|
130 | 126 | </div> |
|
131 | 127 | |
|
132 | 128 | ## REVIEWERS |
|
133 |
<div class="reviewers-title |
|
|
129 | <div class="reviewers-title"> | |
|
134 | 130 | <div class="pr-details-title"> |
|
135 | 131 | ${_('Pull request reviewers')} |
|
136 | 132 | <span class="calculate-reviewers"> - ${_('loading...')}</span> |
|
137 | 133 | </div> |
|
138 | 134 | </div> |
|
139 |
<div id="reviewers" class=" |
|
|
135 | <div id="reviewers" class="pr-details-content reviewers"> | |
|
140 | 136 | ## members goes here, filled via JS based on initial selection ! |
|
141 | 137 | <input type="hidden" name="__start__" value="review_members:sequence"> |
|
142 |
< |
|
|
138 | <table id="review_members" class="group_members"> | |
|
139 | ## This content is loaded via JS and ReviewersPanel | |
|
140 | </table> | |
|
143 | 141 | <input type="hidden" name="__end__" value="review_members:sequence"> |
|
142 | ||
|
144 | 143 | <div id="add_reviewer_input" class='ac'> |
|
145 | 144 | <div class="reviewer_ac"> |
|
146 | 145 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} |
|
147 | 146 | <div id="reviewers_container"></div> |
|
148 | 147 | </div> |
|
149 | 148 | </div> |
|
149 | ||
|
150 | 150 | </div> |
|
151 | 151 | </div> |
|
152 | 152 | </div> |
|
153 | <div class="box"> | |
|
154 | <div> | |
|
155 | ## overview pulled by ajax | |
|
156 | <div id="pull_request_overview"></div> | |
|
153 | ||
|
154 | ## SUBMIT | |
|
155 | <div class="field"> | |
|
156 | <div class="label label-textarea"> | |
|
157 | <label for="pullrequest_submit"></label> | |
|
158 | </div> | |
|
159 | <div class="input"> | |
|
160 | <div class="pr-submit-button"> | |
|
161 | <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}"> | |
|
157 | 162 | </div> |
|
158 | 163 | </div> |
|
164 | </div> | |
|
165 | </div> | |
|
166 | </div> | |
|
167 | </div> | |
|
168 | ||
|
169 | </div> | |
|
170 | ||
|
159 | 171 | ${h.end_form()} |
|
160 | 172 | </div> |
|
161 | 173 | |
@@ -243,8 +255,6 b'' | |||
|
243 | 255 | |
|
244 | 256 | var diffDataHandler = function(data) { |
|
245 | 257 | |
|
246 | $('#pull_request_overview').html(data); | |
|
247 | ||
|
248 | 258 | var commitElements = data['commits']; |
|
249 | 259 | var files = data['files']; |
|
250 | 260 | var added = data['stats'][0] |
@@ -303,27 +313,33 b'' | |||
|
303 | 313 | |
|
304 | 314 | msg += '<input type="hidden" name="__end__" value="revisions:sequence">' |
|
305 | 315 | msg += _ngettext( |
|
306 |
' |
|
|
307 |
' |
|
|
316 | 'Compare summary: <strong>{0} commit</strong>', | |
|
317 | 'Compare summary: <strong>{0} commits</strong>', | |
|
308 | 318 | commitElements.length).format(commitElements.length) |
|
309 | 319 | |
|
310 |
msg += ' |
|
|
320 | msg += ''; | |
|
311 | 321 | msg += _ngettext( |
|
312 |
'<strong>{0} file</strong> changed |
|
|
313 |
'<strong>{0} files</strong> changed |
|
|
322 | '<strong>, and {0} file</strong> changed.', | |
|
323 | '<strong>, and {0} files</strong> changed.', | |
|
314 | 324 | files.length).format(files.length) |
|
315 | msg += '<span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted</span>.'.format(added, deleted) | |
|
316 | 325 | |
|
317 | msg += '\n\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url); | |
|
326 | msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted) | |
|
327 | ||
|
328 | msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url); | |
|
318 | 329 | |
|
319 | 330 | if (commitElements.length) { |
|
320 | 331 | var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length); |
|
321 | 332 | prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare'); |
|
322 | 333 | } |
|
323 | 334 | else { |
|
324 | prButtonLock(true, "${_('There are no commits to merge.')}", 'compare'); | |
|
335 | var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format( | |
|
336 | _gettext('There are no commits to merge.')); | |
|
337 | prButtonLock(true, noCommitsMsg, 'compare'); | |
|
325 | 338 | } |
|
326 | 339 | |
|
340 | //make both panels equal | |
|
341 | $('.target-panel').height($('.source-panel').height()) | |
|
342 | ||
|
327 | 343 | }; |
|
328 | 344 | |
|
329 | 345 | reviewersController = new ReviewersController(); |
@@ -429,10 +445,12 b'' | |||
|
429 | 445 | |
|
430 | 446 | var targetRepoChanged = function(repoData) { |
|
431 | 447 | // generate new DESC of target repo displayed next to select |
|
448 | ||
|
449 | $('#target_repo_desc').html(repoData['description']); | |
|
450 | ||
|
432 | 451 | var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']}); |
|
433 | $('#target_repo_desc').html( | |
|
434 | "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink) | |
|
435 | ); | |
|
452 | var title = _gettext('Switch target repository with the source.') | |
|
453 | $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink)) | |
|
436 | 454 | |
|
437 | 455 | // generate dynamic select2 for refs. |
|
438 | 456 | initTargetRefs(repoData['refs']['select2_refs'], |
This diff has been collapsed as it changes many lines, (570 lines changed) Show them Hide them | |||
@@ -1,6 +1,8 b'' | |||
|
1 | 1 | <%inherit file="/base/base.mako"/> |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> |
|
4 | <%namespace name="sidebar" file="/base/sidebar.mako"/> | |
|
5 | ||
|
4 | 6 | |
|
5 | 7 | <%def name="title()"> |
|
6 | 8 | ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)} |
@@ -21,12 +23,19 b'' | |||
|
21 | 23 | ${self.repo_menu(active='showpullrequest')} |
|
22 | 24 | </%def> |
|
23 | 25 | |
|
26 | ||
|
24 | 27 | <%def name="main()"> |
|
28 | ## Container to gather extracted Tickets | |
|
29 | <% | |
|
30 | c.referenced_commit_issues = [] | |
|
31 | c.referenced_desc_issues = [] | |
|
32 | %> | |
|
25 | 33 | |
|
26 | 34 | <script type="text/javascript"> |
|
27 | 35 | // TODO: marcink switch this to pyroutes |
|
28 | 36 | AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}"; |
|
29 | 37 | templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id}; |
|
38 | templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}'; | |
|
30 | 39 | </script> |
|
31 | 40 | |
|
32 | 41 | <div class="box"> |
@@ -79,7 +88,7 b'' | |||
|
79 | 88 | </div> |
|
80 | 89 | |
|
81 | 90 | <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}"> |
|
82 | ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)} | |
|
91 | ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)} | |
|
83 | 92 | </div> |
|
84 | 93 | |
|
85 | 94 | <div id="pr-desc-edit" class="input textarea" style="display: none;"> |
@@ -89,29 +98,6 b'' | |||
|
89 | 98 | |
|
90 | 99 | <div id="summary" class="fields pr-details-content"> |
|
91 | 100 | |
|
92 | ## review | |
|
93 | <div class="field"> | |
|
94 | <div class="label-pr-detail"> | |
|
95 | <label>${_('Review status')}:</label> | |
|
96 | </div> | |
|
97 | <div class="input"> | |
|
98 | %if c.pull_request_review_status: | |
|
99 | <div class="tag status-tag-${c.pull_request_review_status}"> | |
|
100 | <i class="icon-circle review-status-${c.pull_request_review_status}"></i> | |
|
101 | <span class="changeset-status-lbl"> | |
|
102 | %if c.pull_request.is_closed(): | |
|
103 | ${_('Closed')}, | |
|
104 | %endif | |
|
105 | ||
|
106 | ${h.commit_status_lbl(c.pull_request_review_status)} | |
|
107 | ||
|
108 | </span> | |
|
109 | </div> | |
|
110 | - ${_ungettext('calculated based on {} reviewer vote', 'calculated based on {} reviewers votes', len(c.pull_request_reviewers)).format(len(c.pull_request_reviewers))} | |
|
111 | %endif | |
|
112 | </div> | |
|
113 | </div> | |
|
114 | ||
|
115 | 101 | ## source |
|
116 | 102 | <div class="field"> |
|
117 | 103 | <div class="label-pr-detail"> |
@@ -136,7 +122,7 b'' | |||
|
136 | 122 | |
|
137 | 123 | ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a> |
|
138 | 124 | |
|
139 |
<a class="source-details-action" href="#expand-source-details" onclick="return |
|
|
125 | <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'> | |
|
140 | 126 | <i class="icon-angle-down">more details</i> |
|
141 | 127 | </a> |
|
142 | 128 | |
@@ -231,7 +217,7 b'' | |||
|
231 | 217 | </code> |
|
232 | 218 | </td> |
|
233 | 219 | <td> |
|
234 |
<input ${('checked="checked"' if c.from_version_ |
|
|
220 | <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/> | |
|
235 | 221 | <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/> |
|
236 | 222 | </td> |
|
237 | 223 | <td> |
@@ -280,154 +266,7 b'' | |||
|
280 | 266 | |
|
281 | 267 | </div> |
|
282 | 268 | |
|
283 | ## REVIEW RULES | |
|
284 | <div id="review_rules" style="display: none" class="reviewers-title block-right"> | |
|
285 | <div class="pr-details-title"> | |
|
286 | ${_('Reviewer rules')} | |
|
287 | %if c.allowed_to_update: | |
|
288 | <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> | |
|
289 | %endif | |
|
290 | </div> | |
|
291 | <div class="pr-reviewer-rules"> | |
|
292 | ## review rules will be appended here, by default reviewers logic | |
|
293 | </div> | |
|
294 | <input id="review_data" type="hidden" name="review_data" value=""> | |
|
295 | </div> | |
|
296 | 269 | |
|
297 | ## REVIEWERS | |
|
298 | <div class="reviewers-title first-panel block-right"> | |
|
299 | <div class="pr-details-title"> | |
|
300 | ${_('Pull request reviewers')} | |
|
301 | %if c.allowed_to_update: | |
|
302 | <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span> | |
|
303 | %endif | |
|
304 | </div> | |
|
305 | </div> | |
|
306 | <div id="reviewers" class="block-right pr-details-content reviewers"> | |
|
307 | ||
|
308 | ## members redering block | |
|
309 | <input type="hidden" name="__start__" value="review_members:sequence"> | |
|
310 | <ul id="review_members" class="group_members"> | |
|
311 | ||
|
312 | % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers: | |
|
313 | <script> | |
|
314 | var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n}; | |
|
315 | var status = "${(status[0][1].status if status else 'not_reviewed')}"; | |
|
316 | var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}"; | |
|
317 | var allowed_to_update = ${h.json.dumps(c.allowed_to_update)}; | |
|
318 | ||
|
319 | var entry = renderTemplate('reviewMemberEntry', { | |
|
320 | 'member': member, | |
|
321 | 'mandatory': member.mandatory, | |
|
322 | 'reasons': member.reasons, | |
|
323 | 'allowed_to_update': allowed_to_update, | |
|
324 | 'review_status': status, | |
|
325 | 'review_status_label': status_lbl, | |
|
326 | 'user_group': member.user_group, | |
|
327 | 'create': false | |
|
328 | }); | |
|
329 | $('#review_members').append(entry) | |
|
330 | </script> | |
|
331 | ||
|
332 | % endfor | |
|
333 | ||
|
334 | </ul> | |
|
335 | ||
|
336 | <input type="hidden" name="__end__" value="review_members:sequence"> | |
|
337 | ## end members redering block | |
|
338 | ||
|
339 | %if not c.pull_request.is_closed(): | |
|
340 | <div id="add_reviewer" class="ac" style="display: none;"> | |
|
341 | %if c.allowed_to_update: | |
|
342 | % if not c.forbid_adding_reviewers: | |
|
343 | <div id="add_reviewer_input" class="reviewer_ac"> | |
|
344 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} | |
|
345 | <div id="reviewers_container"></div> | |
|
346 | </div> | |
|
347 | % endif | |
|
348 | <div class="pull-right"> | |
|
349 | <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button> | |
|
350 | </div> | |
|
351 | %endif | |
|
352 | </div> | |
|
353 | %endif | |
|
354 | </div> | |
|
355 | ||
|
356 | ## TODOs will be listed here | |
|
357 | <div class="reviewers-title block-right"> | |
|
358 | <div class="pr-details-title"> | |
|
359 | ## Only show unresolved, that is only what matters | |
|
360 | TODO Comments - ${len(c.unresolved_comments)} / ${(len(c.unresolved_comments) + len(c.resolved_comments))} | |
|
361 | ||
|
362 | % if not c.at_version: | |
|
363 | % if c.resolved_comments: | |
|
364 | <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.unresolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span> | |
|
365 | % else: | |
|
366 | <span class="block-right last-item noselect">Show resolved</span> | |
|
367 | % endif | |
|
368 | % endif | |
|
369 | </div> | |
|
370 | </div> | |
|
371 | <div class="block-right pr-details-content reviewers"> | |
|
372 | ||
|
373 | <table class="todo-table"> | |
|
374 | <% | |
|
375 | def sorter(entry): | |
|
376 | user_id = entry.author.user_id | |
|
377 | resolved = '1' if entry.resolved else '0' | |
|
378 | if user_id == c.rhodecode_user.user_id: | |
|
379 | # own comments first | |
|
380 | user_id = 0 | |
|
381 | return '{}_{}_{}'.format(resolved, user_id, str(entry.comment_id).zfill(100)) | |
|
382 | %> | |
|
383 | ||
|
384 | % if c.at_version: | |
|
385 | <tr> | |
|
386 | <td class="unresolved-todo-text">${_('unresolved TODOs unavailable in this view')}.</td> | |
|
387 | </tr> | |
|
388 | % else: | |
|
389 | % for todo_comment in sorted(c.unresolved_comments + c.resolved_comments, key=sorter): | |
|
390 | <% resolved = todo_comment.resolved %> | |
|
391 | % if inline: | |
|
392 | <% outdated_at_ver = todo_comment.outdated_at_version(getattr(c, 'at_version_num', None)) %> | |
|
393 | % else: | |
|
394 | <% outdated_at_ver = todo_comment.older_than_version(getattr(c, 'at_version_num', None)) %> | |
|
395 | % endif | |
|
396 | ||
|
397 | <tr ${('class="unresolved-todo" style="display: none"' if resolved else '') |n}> | |
|
398 | ||
|
399 | <td class="td-todo-number"> | |
|
400 | % if resolved: | |
|
401 | <a class="permalink todo-resolved tooltip" title="${_('Resolved by comment #{}').format(todo_comment.resolved.comment_id)}" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})"> | |
|
402 | <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a> | |
|
403 | % else: | |
|
404 | <a class="permalink" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})"> | |
|
405 | <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a> | |
|
406 | % endif | |
|
407 | </td> | |
|
408 | <td class="td-todo-gravatar"> | |
|
409 | ${base.gravatar(todo_comment.author.email, 16, user=todo_comment.author, tooltip=True, extra_class=['no-margin'])} | |
|
410 | </td> | |
|
411 | <td class="todo-comment-text-wrapper"> | |
|
412 | <div class="todo-comment-text"> | |
|
413 | <code>${h.chop_at_smart(todo_comment.text, '\n', suffix_if_chopped='...')}</code> | |
|
414 | </div> | |
|
415 | </td> | |
|
416 | ||
|
417 | </tr> | |
|
418 | % endfor | |
|
419 | ||
|
420 | % if len(c.unresolved_comments) == 0: | |
|
421 | <tr> | |
|
422 | <td class="unresolved-todo-text">${_('No unresolved TODOs')}.</td> | |
|
423 | </tr> | |
|
424 | % endif | |
|
425 | ||
|
426 | % endif | |
|
427 | ||
|
428 | </table> | |
|
429 | ||
|
430 | </div> | |
|
431 | 270 | </div> |
|
432 | 271 | |
|
433 | 272 |
|
@@ -484,9 +323,9 b'' | |||
|
484 | 323 | <div class="compare_view_commits_title"> |
|
485 | 324 | % if not c.compare_mode: |
|
486 | 325 | |
|
487 |
% if c.at_version_ |
|
|
326 | % if c.at_version_index: | |
|
488 | 327 | <h4> |
|
489 |
${_('Showing changes at v |
|
|
328 | ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)} | |
|
490 | 329 | </h4> |
|
491 | 330 | % endif |
|
492 | 331 | |
@@ -539,10 +378,11 b'' | |||
|
539 | 378 | </div> |
|
540 | 379 | |
|
541 | 380 | % if not c.missing_commits: |
|
381 | ## COMPARE RANGE DIFF MODE | |
|
542 | 382 | % if c.compare_mode: |
|
543 | 383 | % if c.at_version: |
|
544 | 384 | <h4> |
|
545 |
${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_ |
|
|
385 | ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}: | |
|
546 | 386 | </h4> |
|
547 | 387 | |
|
548 | 388 | <div class="subtitle-compare"> |
@@ -597,7 +437,7 b'' | |||
|
597 | 437 | </td> |
|
598 | 438 | <td class="mid td-description"> |
|
599 | 439 | <div class="log-container truncate-wrap"> |
|
600 | <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div> | |
|
440 | <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div> | |
|
601 | 441 | </div> |
|
602 | 442 | </td> |
|
603 | 443 | </tr> |
@@ -608,19 +448,13 b'' | |||
|
608 | 448 | |
|
609 | 449 | % endif |
|
610 | 450 | |
|
451 | ## Regular DIFF | |
|
611 | 452 | % else: |
|
612 | 453 | <%include file="/compare/compare_commits.mako" /> |
|
613 | 454 | % endif |
|
614 | 455 | |
|
615 | 456 | <div class="cs_files"> |
|
616 | 457 | <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/> |
|
617 | % if c.at_version: | |
|
618 | <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %> | |
|
619 | <% c.comments = c.comment_versions[c.at_version_num]['display'] %> | |
|
620 | % else: | |
|
621 | <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %> | |
|
622 | <% c.comments = c.comment_versions[c.at_version_num]['until'] %> | |
|
623 | % endif | |
|
624 | 458 | |
|
625 | 459 | <% |
|
626 | 460 | pr_menu_data = { |
@@ -667,7 +501,7 b'' | |||
|
667 | 501 | ## comments heading with count |
|
668 | 502 | <div class="comments-heading"> |
|
669 | 503 | <i class="icon-comment"></i> |
|
670 | ${_('Comments')} ${len(c.comments)} | |
|
504 | ${_('General Comments')} ${len(c.comments)} | |
|
671 | 505 | </div> |
|
672 | 506 | |
|
673 | 507 | ## render general comments |
@@ -704,6 +538,271 b'' | |||
|
704 | 538 | % endif |
|
705 | 539 | </div> |
|
706 | 540 | |
|
541 | ||
|
542 | ### NAV SIDEBAR | |
|
543 | <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none"> | |
|
544 | <div class="sidenav navbar__inner" > | |
|
545 | ## TOGGLE | |
|
546 | <div class="sidebar-toggle" onclick="toggleSidebar(); return false"> | |
|
547 | <a href="#toggleSidebar" class="grey-link-action"> | |
|
548 | ||
|
549 | </a> | |
|
550 | </div> | |
|
551 | ||
|
552 | ## CONTENT | |
|
553 | <div class="sidebar-content"> | |
|
554 | ||
|
555 | ## RULES SUMMARY/RULES | |
|
556 | <div class="sidebar-element clear-both"> | |
|
557 | <% vote_title = _ungettext( | |
|
558 | 'Status calculated based on votes from {} reviewer', | |
|
559 | 'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers)) | |
|
560 | %> | |
|
561 | ||
|
562 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> | |
|
563 | <i class="icon-circle review-status-${c.pull_request_review_status}"></i> | |
|
564 | ${len(c.allowed_reviewers)} | |
|
565 | </div> | |
|
566 | ||
|
567 | ## REVIEW RULES | |
|
568 | <div id="review_rules" style="display: none" class=""> | |
|
569 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
570 | <span class="sidebar-heading"> | |
|
571 | ${_('Reviewer rules')} | |
|
572 | </span> | |
|
573 | ||
|
574 | </div> | |
|
575 | <div class="pr-reviewer-rules"> | |
|
576 | ## review rules will be appended here, by default reviewers logic | |
|
577 | </div> | |
|
578 | <input id="review_data" type="hidden" name="review_data" value=""> | |
|
579 | </div> | |
|
580 | ||
|
581 | ## REVIEWERS | |
|
582 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
583 | <span class="tooltip sidebar-heading" title="${vote_title}"> | |
|
584 | <i class="icon-circle review-status-${c.pull_request_review_status}"></i> | |
|
585 | ${_('Reviewers')} | |
|
586 | </span> | |
|
587 | %if c.allowed_to_update: | |
|
588 | <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span> | |
|
589 | <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> | |
|
590 | %else: | |
|
591 | <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span> | |
|
592 | <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> | |
|
593 | %endif | |
|
594 | </div> | |
|
595 | ||
|
596 | <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers"> | |
|
597 | ||
|
598 | ## members redering block | |
|
599 | <input type="hidden" name="__start__" value="review_members:sequence"> | |
|
600 | ||
|
601 | <table id="review_members" class="group_members"> | |
|
602 | ## This content is loaded via JS and ReviewersPanel | |
|
603 | </table> | |
|
604 | ||
|
605 | <input type="hidden" name="__end__" value="review_members:sequence"> | |
|
606 | ## end members redering block | |
|
607 | ||
|
608 | %if not c.pull_request.is_closed(): | |
|
609 | <div id="add_reviewer" class="ac" style="display: none;"> | |
|
610 | %if c.allowed_to_update: | |
|
611 | % if not c.forbid_adding_reviewers: | |
|
612 | <div id="add_reviewer_input" class="reviewer_ac"> | |
|
613 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} | |
|
614 | <div id="reviewers_container"></div> | |
|
615 | </div> | |
|
616 | % endif | |
|
617 | <div class="pull-right"> | |
|
618 | <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button> | |
|
619 | </div> | |
|
620 | %endif | |
|
621 | </div> | |
|
622 | %endif | |
|
623 | </div> | |
|
624 | </div> | |
|
625 | ||
|
626 | ## ## OBSERVERS | |
|
627 | ## <div class="sidebar-element clear-both"> | |
|
628 | ## <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}"> | |
|
629 | ## <i class="icon-eye"></i> | |
|
630 | ## 0 | |
|
631 | ## </div> | |
|
632 | ## | |
|
633 | ## <div class="right-sidebar-expanded-state pr-details-title"> | |
|
634 | ## <span class="sidebar-heading"> | |
|
635 | ## <i class="icon-eye"></i> | |
|
636 | ## ${_('Observers')} | |
|
637 | ## </span> | |
|
638 | ## </div> | |
|
639 | ## <div class="right-sidebar-expanded-state pr-details-content"> | |
|
640 | ## No observers | |
|
641 | ## </div> | |
|
642 | ## </div> | |
|
643 | ||
|
644 | ## TODOs | |
|
645 | <div class="sidebar-element clear-both"> | |
|
646 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs"> | |
|
647 | <i class="icon-flag-filled"></i> | |
|
648 | <span id="todos-count">${len(c.unresolved_comments)}</span> | |
|
649 | </div> | |
|
650 | ||
|
651 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
652 | ## Only show unresolved, that is only what matters | |
|
653 | <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false"> | |
|
654 | <i class="icon-flag-filled"></i> | |
|
655 | TODOs | |
|
656 | </span> | |
|
657 | ||
|
658 | % if not c.at_version: | |
|
659 | % if c.resolved_comments: | |
|
660 | <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span> | |
|
661 | % else: | |
|
662 | <span class="block-right last-item noselect">Show resolved</span> | |
|
663 | % endif | |
|
664 | % endif | |
|
665 | </div> | |
|
666 | ||
|
667 | <div class="right-sidebar-expanded-state pr-details-content"> | |
|
668 | ||
|
669 | % if c.at_version: | |
|
670 | <table> | |
|
671 | <tr> | |
|
672 | <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td> | |
|
673 | </tr> | |
|
674 | </table> | |
|
675 | % else: | |
|
676 | % if c.unresolved_comments + c.resolved_comments: | |
|
677 | ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)} | |
|
678 | % else: | |
|
679 | <table> | |
|
680 | <tr> | |
|
681 | <td> | |
|
682 | ${_('No TODOs yet')} | |
|
683 | </td> | |
|
684 | </tr> | |
|
685 | </table> | |
|
686 | % endif | |
|
687 | % endif | |
|
688 | </div> | |
|
689 | </div> | |
|
690 | ||
|
691 | ## COMMENTS | |
|
692 | <div class="sidebar-element clear-both"> | |
|
693 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}"> | |
|
694 | <i class="icon-comment" style="color: #949494"></i> | |
|
695 | <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span> | |
|
696 | <span class="display-none" id="general-comments-count">${len(c.comments)}</span> | |
|
697 | <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span> | |
|
698 | </div> | |
|
699 | ||
|
700 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
701 | <span class="sidebar-heading noselect" onclick="refreshComments(); return false"> | |
|
702 | <i class="icon-comment" style="color: #949494"></i> | |
|
703 | ${_('Comments')} | |
|
704 | ||
|
705 | ## % if outdated_comm_count_ver: | |
|
706 | ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;"> | |
|
707 | ## (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
708 | ## </a> | |
|
709 | ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a> | |
|
710 | ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a> | |
|
711 | ||
|
712 | ## % else: | |
|
713 | ## (${_("{} Outdated").format(outdated_comm_count_ver)}) | |
|
714 | ## % endif | |
|
715 | ||
|
716 | </span> | |
|
717 | ||
|
718 | % if outdated_comm_count_ver: | |
|
719 | <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span> | |
|
720 | % else: | |
|
721 | <span class="block-right last-item noselect">Show hidden</span> | |
|
722 | % endif | |
|
723 | ||
|
724 | </div> | |
|
725 | ||
|
726 | <div class="right-sidebar-expanded-state pr-details-content"> | |
|
727 | % if c.inline_comments_flat + c.comments: | |
|
728 | ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))} | |
|
729 | % else: | |
|
730 | <table> | |
|
731 | <tr> | |
|
732 | <td> | |
|
733 | ${_('No Comments yet')} | |
|
734 | </td> | |
|
735 | </tr> | |
|
736 | </table> | |
|
737 | % endif | |
|
738 | </div> | |
|
739 | ||
|
740 | </div> | |
|
741 | ||
|
742 | ## Referenced Tickets | |
|
743 | <div class="sidebar-element clear-both"> | |
|
744 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}"> | |
|
745 | <i class="icon-info-circled"></i> | |
|
746 | ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))} | |
|
747 | </div> | |
|
748 | ||
|
749 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
750 | <span class="sidebar-heading"> | |
|
751 | <i class="icon-info-circled"></i> | |
|
752 | ${_('Referenced Tickets')} | |
|
753 | </span> | |
|
754 | </div> | |
|
755 | <div class="right-sidebar-expanded-state pr-details-content"> | |
|
756 | <table> | |
|
757 | ||
|
758 | <tr><td><code>${_('In pull request description')}:</code></td></tr> | |
|
759 | % if c.referenced_desc_issues: | |
|
760 | % for ticket_dict in c.referenced_desc_issues: | |
|
761 | <tr> | |
|
762 | <td> | |
|
763 | <a href="${ticket_dict.get('url')}"> | |
|
764 | ${ticket_dict.get('id')} | |
|
765 | </a> | |
|
766 | </td> | |
|
767 | </tr> | |
|
768 | % endfor | |
|
769 | % else: | |
|
770 | <tr> | |
|
771 | <td> | |
|
772 | ${_('No Ticket data found.')} | |
|
773 | </td> | |
|
774 | </tr> | |
|
775 | % endif | |
|
776 | ||
|
777 | <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr> | |
|
778 | % if c.referenced_commit_issues: | |
|
779 | % for ticket_dict in c.referenced_commit_issues: | |
|
780 | <tr> | |
|
781 | <td> | |
|
782 | <a href="${ticket_dict.get('url')}"> | |
|
783 | ${ticket_dict.get('id')} | |
|
784 | </a> | |
|
785 | </td> | |
|
786 | </tr> | |
|
787 | % endfor | |
|
788 | % else: | |
|
789 | <tr> | |
|
790 | <td> | |
|
791 | ${_('No Ticket data found.')} | |
|
792 | </td> | |
|
793 | </tr> | |
|
794 | % endif | |
|
795 | </table> | |
|
796 | ||
|
797 | </div> | |
|
798 | </div> | |
|
799 | ||
|
800 | </div> | |
|
801 | ||
|
802 | </div> | |
|
803 | </aside> | |
|
804 | ||
|
805 | ## This JS needs to be at the end | |
|
707 | 806 |
|
|
708 | 807 | |
|
709 | 808 |
|
@@ -714,7 +813,11 b'' | |||
|
714 | 813 | |
|
715 | 814 |
|
|
716 | 815 | |
|
717 | $(function () { | |
|
816 | window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n}; | |
|
817 | window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n}; | |
|
818 | ||
|
819 | (function () { | |
|
820 | "use strict"; | |
|
718 | 821 | |
|
719 | 822 |
|
|
720 | 823 |
|
@@ -737,12 +840,13 b'' | |||
|
737 | 840 |
|
|
738 | 841 | |
|
739 | 842 |
|
|
843 | var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm; | |
|
740 | 844 |
|
|
741 | 845 |
|
|
742 | 846 |
|
|
743 | 847 |
|
|
744 | 848 |
|
|
745 |
|
|
|
849 | cmInstance.refresh(); | |
|
746 | 850 |
|
|
747 | 851 | |
|
748 | 852 |
|
@@ -754,60 +858,24 b'' | |||
|
754 | 858 |
|
|
755 | 859 |
|
|
756 | 860 | |
|
757 | var ReviewersPanel = { | |
|
758 | editButton: $('#open_edit_reviewers'), | |
|
759 | closeButton: $('#close_edit_reviewers'), | |
|
760 | addButton: $('#add_reviewer'), | |
|
761 | removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'), | |
|
762 | ||
|
763 | init: function () { | |
|
764 | var self = this; | |
|
765 | this.editButton.on('click', function (e) { | |
|
766 | self.edit(); | |
|
767 | }); | |
|
768 | this.closeButton.on('click', function (e) { | |
|
769 | self.close(); | |
|
770 | }); | |
|
771 | }, | |
|
861 | PRDetails.init(); | |
|
862 | ReviewersPanel.init(reviewerRulesData, setReviewersData); | |
|
772 | 863 | |
|
773 | edit: function (event) { | |
|
774 | this.editButton.hide(); | |
|
775 | this.closeButton.show(); | |
|
776 | this.addButton.show(); | |
|
777 | this.removeButtons.css('visibility', 'visible'); | |
|
778 | // review rules | |
|
779 | reviewersController.loadReviewRules( | |
|
780 | ${c.pull_request.reviewer_data_json | n}); | |
|
781 | }, | |
|
782 | ||
|
783 | close: function (event) { | |
|
784 | this.editButton.show(); | |
|
785 | this.closeButton.hide(); | |
|
786 | this.addButton.hide(); | |
|
787 | this.removeButtons.css('visibility', 'hidden'); | |
|
788 | // hide review rules | |
|
789 | reviewersController.hideReviewRules() | |
|
790 | } | |
|
791 | }; | |
|
792 | ||
|
793 | PRDetails.init(); | |
|
794 | ReviewersPanel.init(); | |
|
795 | ||
|
796 | showOutdated = function (self) { | |
|
864 | window.showOutdated = function (self) { | |
|
797 | 865 |
|
|
798 | 866 |
|
|
799 | 867 |
|
|
800 | 868 |
|
|
801 | 869 |
|
|
802 | 870 | |
|
803 |
|
|
|
871 | window.hideOutdated = function (self) { | |
|
804 | 872 |
|
|
805 | 873 |
|
|
806 | 874 |
|
|
807 | 875 |
|
|
808 | 876 |
|
|
809 | 877 | |
|
810 |
|
|
|
878 | window.refreshMergeChecks = function () { | |
|
811 | 879 |
|
|
812 | 880 |
|
|
813 | 881 |
|
@@ -821,7 +889,7 b'' | |||
|
821 | 889 |
|
|
822 | 890 |
|
|
823 | 891 | |
|
824 |
|
|
|
892 | window.closePullRequest = function (status) { | |
|
825 | 893 |
|
|
826 | 894 |
|
|
827 | 895 |
|
@@ -831,6 +899,7 b'' | |||
|
831 | 899 |
|
|
832 | 900 |
|
|
833 | 901 | |
|
902 | //TODO this functionality is now missing | |
|
834 | 903 |
|
|
835 | 904 |
|
|
836 | 905 |
|
@@ -844,22 +913,6 b'' | |||
|
844 | 913 |
|
|
845 | 914 |
|
|
846 | 915 | |
|
847 | $('.show-inline-comments').on('change', function (e) { | |
|
848 | var show = 'none'; | |
|
849 | var target = e.currentTarget; | |
|
850 | if (target.checked) { | |
|
851 | show = '' | |
|
852 | } | |
|
853 | var boxid = $(target).attr('id_for'); | |
|
854 | var comments = $('#{0} .inline-comments'.format(boxid)); | |
|
855 | var fn_display = function (idx) { | |
|
856 | $(this).css('display', show); | |
|
857 | }; | |
|
858 | $(comments).each(fn_display); | |
|
859 | var btns = $('#{0} .inline-comments-button'.format(boxid)); | |
|
860 | $(btns).each(fn_display); | |
|
861 | }); | |
|
862 | ||
|
863 | 916 |
|
|
864 | 917 |
|
|
865 | 918 |
|
@@ -884,7 +937,6 b'' | |||
|
884 | 937 |
|
|
885 | 938 |
|
|
886 | 939 | |
|
887 | ||
|
888 | 940 |
|
|
889 | 941 |
|
|
890 | 942 | |
@@ -905,6 +957,22 b'' | |||
|
905 | 957 |
|
|
906 | 958 |
|
|
907 | 959 | |
|
960 | $('.show-inline-comments').on('change', function (e) { | |
|
961 | var show = 'none'; | |
|
962 | var target = e.currentTarget; | |
|
963 | if (target.checked) { | |
|
964 | show = '' | |
|
965 | } | |
|
966 | var boxid = $(target).attr('id_for'); | |
|
967 | var comments = $('#{0} .inline-comments'.format(boxid)); | |
|
968 | var fn_display = function (idx) { | |
|
969 | $(this).css('display', show); | |
|
970 | }; | |
|
971 | $(comments).each(fn_display); | |
|
972 | var btns = $('#{0} .inline-comments-button'.format(boxid)); | |
|
973 | $(btns).each(fn_display); | |
|
974 | }); | |
|
975 | ||
|
908 | 976 |
|
|
909 | 977 |
|
|
910 | 978 |
|
@@ -912,10 +980,14 b'' | |||
|
912 | 980 | |
|
913 | 981 |
|
|
914 | 982 | |
|
983 | })(); | |
|
984 | ||
|
985 | $(document).ready(function () { | |
|
986 | ||
|
987 | var channel = '${c.pr_broadcast_channel}'; | |
|
988 | new ReviewerPresenceController(channel) | |
|
989 | ||
|
915 | 990 | }) |
|
916 | ||
|
917 | 991 |
|
|
918 | 992 | |
|
919 | </div> | |
|
920 | ||
|
921 | 993 | </%def> |
@@ -948,8 +948,8 b' def assert_inline_comments(pull_request,' | |||
|
948 | 948 | if visible is not None: |
|
949 | 949 | inline_comments = CommentsModel().get_inline_comments( |
|
950 | 950 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
951 |
inline_cnt = CommentsModel().get_inline_comments_ |
|
|
952 | inline_comments) | |
|
951 | inline_cnt = len(CommentsModel().get_inline_comments_as_list( | |
|
952 | inline_comments)) | |
|
953 | 953 | assert inline_cnt == visible |
|
954 | 954 | if outdated is not None: |
|
955 | 955 | outdated_comments = CommentsModel().get_outdated_comments( |
General Comments 0
You need to be logged in to leave comments.
Login now