Show More
@@ -0,0 +1,68 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.RepoReviewRuleUser.__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_rule_user_role(op, meta.Session) | |||
|
35 | ||||
|
36 | table = db.RepoReviewRuleUserGroup.__table__ | |||
|
37 | with op.batch_alter_table(table.name) as batch_op: | |||
|
38 | new_column = Column('role', Unicode(255), nullable=True) | |||
|
39 | batch_op.add_column(new_column) | |||
|
40 | ||||
|
41 | _fill_rule_user_group_role(op, meta.Session) | |||
|
42 | ||||
|
43 | ||||
|
44 | def downgrade(migrate_engine): | |||
|
45 | meta = MetaData() | |||
|
46 | meta.bind = migrate_engine | |||
|
47 | ||||
|
48 | ||||
|
49 | def fixups(models, _SESSION): | |||
|
50 | pass | |||
|
51 | ||||
|
52 | ||||
|
53 | def _fill_rule_user_role(op, session): | |||
|
54 | params = {'role': 'reviewer'} | |||
|
55 | query = text( | |||
|
56 | 'UPDATE repo_review_rules_users SET role = :role' | |||
|
57 | ).bindparams(**params) | |||
|
58 | op.execute(query) | |||
|
59 | session().commit() | |||
|
60 | ||||
|
61 | ||||
|
62 | def _fill_rule_user_group_role(op, session): | |||
|
63 | params = {'role': 'reviewer'} | |||
|
64 | query = text( | |||
|
65 | 'UPDATE repo_review_rules_users_groups SET role = :role' | |||
|
66 | ).bindparams(**params) | |||
|
67 | op.execute(query) | |||
|
68 | session().commit() |
@@ -0,0 +1,35 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2020 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | ||||
|
22 | def html(info): | |||
|
23 | """ | |||
|
24 | Custom string as html content_type renderer for pyramid | |||
|
25 | """ | |||
|
26 | def _render(value, system): | |||
|
27 | request = system.get('request') | |||
|
28 | if request is not None: | |||
|
29 | response = request.response | |||
|
30 | ct = response.content_type | |||
|
31 | if ct == response.default_content_type: | |||
|
32 | response.content_type = 'text/html' | |||
|
33 | return value | |||
|
34 | ||||
|
35 | return _render |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||||
48 | EXTENSIONS = {} |
|
48 | EXTENSIONS = {} | |
49 |
|
49 | |||
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) | |
51 |
__dbversion__ = 10 |
|
51 | __dbversion__ = 110 # defines current db version for migrations | |
52 | __platform__ = platform.system() |
|
52 | __platform__ = platform.system() | |
53 | __license__ = 'AGPLv3, and Commercial License' |
|
53 | __license__ = 'AGPLv3, and Commercial License' | |
54 | __author__ = 'RhodeCode GmbH' |
|
54 | __author__ = 'RhodeCode GmbH' |
@@ -704,7 +704,7 b' def create_pull_request(' | |||||
704 | user = get_user_or_error(reviewer_object['username']) |
|
704 | user = get_user_or_error(reviewer_object['username']) | |
705 | reviewer_object['user_id'] = user.user_id |
|
705 | reviewer_object['user_id'] = user.user_id | |
706 |
|
706 | |||
707 | get_default_reviewers_data, validate_default_reviewers = \ |
|
707 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
708 | PullRequestModel().get_reviewer_functions() |
|
708 | PullRequestModel().get_reviewer_functions() | |
709 |
|
709 | |||
710 | # recalculate reviewers logic, to make sure we can validate this |
|
710 | # recalculate reviewers logic, to make sure we can validate this | |
@@ -865,14 +865,13 b' def update_pull_request(' | |||||
865 | user = get_user_or_error(reviewer_object['username']) |
|
865 | user = get_user_or_error(reviewer_object['username']) | |
866 | reviewer_object['user_id'] = user.user_id |
|
866 | reviewer_object['user_id'] = user.user_id | |
867 |
|
867 | |||
868 | get_default_reviewers_data, get_validated_reviewers = \ |
|
868 | get_default_reviewers_data, get_validated_reviewers, validate_observers = \ | |
869 | PullRequestModel().get_reviewer_functions() |
|
869 | PullRequestModel().get_reviewer_functions() | |
870 |
|
870 | |||
871 | # re-use stored rules |
|
871 | # re-use stored rules | |
872 | reviewer_rules = pull_request.reviewer_data |
|
872 | reviewer_rules = pull_request.reviewer_data | |
873 | try: |
|
873 | try: | |
874 | reviewers = get_validated_reviewers( |
|
874 | reviewers = get_validated_reviewers(reviewer_objects, reviewer_rules) | |
875 | reviewer_objects, reviewer_rules) |
|
|||
876 | except ValueError as e: |
|
875 | except ValueError as e: | |
877 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
876 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) | |
878 | else: |
|
877 | else: |
@@ -34,6 +34,7 b' log = logging.getLogger(__name__)' | |||||
34 |
|
34 | |||
35 |
|
35 | |||
36 | class DebugStyleView(BaseAppView): |
|
36 | class DebugStyleView(BaseAppView): | |
|
37 | ||||
37 | def load_default_context(self): |
|
38 | def load_default_context(self): | |
38 | c = self._get_local_tmpl_context() |
|
39 | c = self._get_local_tmpl_context() | |
39 |
|
40 | |||
@@ -75,6 +76,7 b' Check if we should use full-topic or min' | |||||
75 | source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'), |
|
76 | source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'), | |
76 | target_ref_parts=AttributeDict(type='branch', name='master'), |
|
77 | target_ref_parts=AttributeDict(type='branch', name='master'), | |
77 | ) |
|
78 | ) | |
|
79 | ||||
78 | target_repo = AttributeDict(repo_name='repo_group/target_repo') |
|
80 | target_repo = AttributeDict(repo_name='repo_group/target_repo') | |
79 | source_repo = AttributeDict(repo_name='repo_group/source_repo') |
|
81 | source_repo = AttributeDict(repo_name='repo_group/source_repo') | |
80 | user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user |
|
82 | user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user | |
@@ -83,6 +85,7 b' Check if we should use full-topic or min' | |||||
83 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], |
|
85 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], | |
84 | 'removed': ['eeeeeeeeeee'], |
|
86 | 'removed': ['eeeeeeeeeee'], | |
85 | }) |
|
87 | }) | |
|
88 | ||||
86 | file_changes = AttributeDict({ |
|
89 | file_changes = AttributeDict({ | |
87 | 'added': ['a/file1.md', 'file2.py'], |
|
90 | 'added': ['a/file1.md', 'file2.py'], | |
88 | 'modified': ['b/modified_file.rst'], |
|
91 | 'modified': ['b/modified_file.rst'], | |
@@ -97,15 +100,19 b' Check if we should use full-topic or min' | |||||
97 | 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n', |
|
100 | 'exc_message': 'Traceback (most recent call last):\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/tweens.py", line 41, in excview_tween\n response = handler(request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/router.py", line 148, in handle_request\n registry, request, context, context_iface, view_name\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/view.py", line 667, in _call_view\n response = view_callable(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 188, in attr_view\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/config/views.py", line 214, in predicate_wrapper\n return view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 401, in viewresult_to_response\n result = view(context, request)\n File "/nix/store/s43k2r9rysfbzmsjdqnxgzvvb7zjhkxb-python2.7-pyramid-1.10.4/lib/python2.7/site-packages/pyramid/viewderivers.py", line 132, in _class_view\n response = getattr(inst, attr)()\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/apps/debug_style/views.py", line 355, in render_email\n template_type, **email_kwargs.get(email_id, {}))\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/model/notification.py", line 402, in render_email\n body = email_template.render(None, **_kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 95, in render\n return self._render_with_exc(tmpl, args, kwargs)\n File "/mnt/hgfs/marcink/workspace/rhodecode-enterprise-ce/rhodecode/lib/partial_renderer.py", line 79, in _render_with_exc\n return render_func.render(*args, **kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/template.py", line 476, in render\n return runtime._render(self, self.callable_, args, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 883, in _render\n **_kwargs_for_callable(callable_, data)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 920, in _render_context\n _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)\n File "/nix/store/dakh34sxz4yfr435c0cwjz0sd6hnd5g3-python2.7-mako-1.1.0/lib/python2.7/site-packages/mako/runtime.py", line 947, in _exec_template\n callable_(context, *args, **kwargs)\n File "rhodecode_templates_email_templates_base_mako", line 63, in render_body\n File "rhodecode_templates_email_templates_exception_tracker_mako", line 43, in render_body\nAttributeError: \'str\' object has no attribute \'get\'\n', | |
98 | 'exc_type': 'AttributeError' |
|
101 | 'exc_type': 'AttributeError' | |
99 | } |
|
102 | } | |
|
103 | ||||
100 | email_kwargs = { |
|
104 | email_kwargs = { | |
101 | 'test': {}, |
|
105 | 'test': {}, | |
|
106 | ||||
102 | 'message': { |
|
107 | 'message': { | |
103 | 'body': 'message body !' |
|
108 | 'body': 'message body !' | |
104 | }, |
|
109 | }, | |
|
110 | ||||
105 | 'email_test': { |
|
111 | 'email_test': { | |
106 | 'user': user, |
|
112 | 'user': user, | |
107 | 'date': datetime.datetime.now(), |
|
113 | 'date': datetime.datetime.now(), | |
108 | }, |
|
114 | }, | |
|
115 | ||||
109 | 'exception': { |
|
116 | 'exception': { | |
110 | 'email_prefix': '[RHODECODE ERROR]', |
|
117 | 'email_prefix': '[RHODECODE ERROR]', | |
111 | 'exc_id': exc_traceback['exc_id'], |
|
118 | 'exc_id': exc_traceback['exc_id'], | |
@@ -113,6 +120,7 b' Check if we should use full-topic or min' | |||||
113 | 'exc_type_name': 'NameError', |
|
120 | 'exc_type_name': 'NameError', | |
114 | 'exc_traceback': exc_traceback, |
|
121 | 'exc_traceback': exc_traceback, | |
115 | }, |
|
122 | }, | |
|
123 | ||||
116 | 'password_reset': { |
|
124 | 'password_reset': { | |
117 | 'password_reset_url': 'http://example.com/reset-rhodecode-password/token', |
|
125 | 'password_reset_url': 'http://example.com/reset-rhodecode-password/token', | |
118 |
|
126 | |||
@@ -121,6 +129,7 b' Check if we should use full-topic or min' | |||||
121 | 'email': 'test@rhodecode.com', |
|
129 | 'email': 'test@rhodecode.com', | |
122 | 'first_admin_email': User.get_first_super_admin().email |
|
130 | 'first_admin_email': User.get_first_super_admin().email | |
123 | }, |
|
131 | }, | |
|
132 | ||||
124 | 'password_reset_confirmation': { |
|
133 | 'password_reset_confirmation': { | |
125 | 'new_password': 'new-password-example', |
|
134 | 'new_password': 'new-password-example', | |
126 | 'user': user, |
|
135 | 'user': user, | |
@@ -128,6 +137,7 b' Check if we should use full-topic or min' | |||||
128 | 'email': 'test@rhodecode.com', |
|
137 | 'email': 'test@rhodecode.com', | |
129 | 'first_admin_email': User.get_first_super_admin().email |
|
138 | 'first_admin_email': User.get_first_super_admin().email | |
130 | }, |
|
139 | }, | |
|
140 | ||||
131 | 'registration': { |
|
141 | 'registration': { | |
132 | 'user': user, |
|
142 | 'user': user, | |
133 | 'date': datetime.datetime.now(), |
|
143 | 'date': datetime.datetime.now(), | |
@@ -161,6 +171,7 b' Check if we should use full-topic or min' | |||||
161 | 'mention': True, |
|
171 | 'mention': True, | |
162 |
|
172 | |||
163 | }, |
|
173 | }, | |
|
174 | ||||
164 | 'pull_request_comment+status': { |
|
175 | 'pull_request_comment+status': { | |
165 | 'user': user, |
|
176 | 'user': user, | |
166 |
|
177 | |||
@@ -201,6 +212,7 b' def db():' | |||||
201 | 'mention': True, |
|
212 | 'mention': True, | |
202 |
|
213 | |||
203 | }, |
|
214 | }, | |
|
215 | ||||
204 | 'pull_request_comment+file': { |
|
216 | 'pull_request_comment+file': { | |
205 | 'user': user, |
|
217 | 'user': user, | |
206 |
|
218 | |||
@@ -303,6 +315,7 b' This should work better !' | |||||
303 | 'renderer_type': 'markdown', |
|
315 | 'renderer_type': 'markdown', | |
304 | 'mention': True, |
|
316 | 'mention': True, | |
305 | }, |
|
317 | }, | |
|
318 | ||||
306 | 'cs_comment+status': { |
|
319 | 'cs_comment+status': { | |
307 | 'user': user, |
|
320 | 'user': user, | |
308 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
|
321 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), | |
@@ -328,6 +341,7 b' This is a multiline comment :)' | |||||
328 | 'renderer_type': 'markdown', |
|
341 | 'renderer_type': 'markdown', | |
329 | 'mention': True, |
|
342 | 'mention': True, | |
330 | }, |
|
343 | }, | |
|
344 | ||||
331 | 'cs_comment+file': { |
|
345 | 'cs_comment+file': { | |
332 | 'user': user, |
|
346 | 'user': user, | |
333 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
|
347 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), | |
@@ -371,8 +385,58 b' users: description edit fixes' | |||||
371 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
385 | 'pull_request_source_repo_url': 'http://source-repo/url', | |
372 |
|
386 | |||
373 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', |
|
387 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |
|
388 | 'user_role': 'reviewer', | |||
|
389 | }, | |||
|
390 | ||||
|
391 | 'pull_request+reviewer_role': { | |||
|
392 | 'user': user, | |||
|
393 | 'pull_request': pr, | |||
|
394 | 'pull_request_commits': [ | |||
|
395 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ | |||
|
396 | my-account: moved email closer to profile as it's similar data just moved outside. | |||
|
397 | '''), | |||
|
398 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ | |||
|
399 | users: description edit fixes | |||
|
400 | ||||
|
401 | - tests | |||
|
402 | - added metatags info | |||
|
403 | '''), | |||
|
404 | ], | |||
|
405 | ||||
|
406 | 'pull_request_target_repo': target_repo, | |||
|
407 | 'pull_request_target_repo_url': 'http://target-repo/url', | |||
|
408 | ||||
|
409 | 'pull_request_source_repo': source_repo, | |||
|
410 | 'pull_request_source_repo_url': 'http://source-repo/url', | |||
|
411 | ||||
|
412 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |||
|
413 | 'user_role': 'reviewer', | |||
|
414 | }, | |||
|
415 | ||||
|
416 | 'pull_request+observer_role': { | |||
|
417 | 'user': user, | |||
|
418 | 'pull_request': pr, | |||
|
419 | 'pull_request_commits': [ | |||
|
420 | ('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\ | |||
|
421 | my-account: moved email closer to profile as it's similar data just moved outside. | |||
|
422 | '''), | |||
|
423 | ('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\ | |||
|
424 | users: description edit fixes | |||
|
425 | ||||
|
426 | - tests | |||
|
427 | - added metatags info | |||
|
428 | '''), | |||
|
429 | ], | |||
|
430 | ||||
|
431 | 'pull_request_target_repo': target_repo, | |||
|
432 | 'pull_request_target_repo_url': 'http://target-repo/url', | |||
|
433 | ||||
|
434 | 'pull_request_source_repo': source_repo, | |||
|
435 | 'pull_request_source_repo_url': 'http://source-repo/url', | |||
|
436 | ||||
|
437 | 'pull_request_url': 'http://code.rhodecode.com/_pull-request/123', | |||
|
438 | 'user_role': 'observer' | |||
374 | } |
|
439 | } | |
375 |
|
||||
376 | } |
|
440 | } | |
377 |
|
441 | |||
378 | template_type = email_id.split('+')[0] |
|
442 | template_type = email_id.split('+')[0] | |
@@ -401,6 +465,7 b' users: description edit fixes' | |||||
401 | c = self.load_default_context() |
|
465 | c = self.load_default_context() | |
402 | c.active = os.path.splitext(t_path)[0] |
|
466 | c.active = os.path.splitext(t_path)[0] | |
403 | c.came_from = '' |
|
467 | c.came_from = '' | |
|
468 | # NOTE(marcink): extend the email types with variations based on data sets | |||
404 | c.email_types = { |
|
469 | c.email_types = { | |
405 | 'cs_comment+file': {}, |
|
470 | 'cs_comment+file': {}, | |
406 | 'cs_comment+status': {}, |
|
471 | 'cs_comment+status': {}, | |
@@ -409,6 +474,9 b' users: description edit fixes' | |||||
409 | 'pull_request_comment+status': {}, |
|
474 | 'pull_request_comment+status': {}, | |
410 |
|
475 | |||
411 | 'pull_request_update': {}, |
|
476 | 'pull_request_update': {}, | |
|
477 | ||||
|
478 | 'pull_request+reviewer_role': {}, | |||
|
479 | 'pull_request+observer_role': {}, | |||
412 | } |
|
480 | } | |
413 | c.email_types.update(EmailNotificationModel.email_types) |
|
481 | c.email_types.update(EmailNotificationModel.email_types) | |
414 |
|
482 |
@@ -21,11 +21,13 b'' | |||||
21 | from rhodecode.lib import helpers as h |
|
21 | from rhodecode.lib import helpers as h | |
22 | from rhodecode.lib.utils2 import safe_int |
|
22 | from rhodecode.lib.utils2 import safe_int | |
23 | from rhodecode.model.pull_request import get_diff_info |
|
23 | from rhodecode.model.pull_request import get_diff_info | |
24 |
|
24 | from rhodecode.model.db import PullRequestReviewers | ||
25 | REVIEWER_API_VERSION = 'V3' |
|
25 | # V3 - Reviewers, with default rules data | |
|
26 | # v4 - Added observers metadata | |||
|
27 | REVIEWER_API_VERSION = 'V4' | |||
26 |
|
28 | |||
27 |
|
29 | |||
28 | def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group=None): |
|
30 | def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None): | |
29 | """ |
|
31 | """ | |
30 | Returns json struct of a reviewer for frontend |
|
32 | Returns json struct of a reviewer for frontend | |
31 |
|
33 | |||
@@ -33,11 +35,15 b' def reviewer_as_json(user, reasons=None,' | |||||
33 | :param reasons: list of strings of why they are reviewers |
|
35 | :param reasons: list of strings of why they are reviewers | |
34 | :param mandatory: bool, to set user as mandatory |
|
36 | :param mandatory: bool, to set user as mandatory | |
35 | """ |
|
37 | """ | |
|
38 | role = role or PullRequestReviewers.ROLE_REVIEWER | |||
|
39 | if role not in PullRequestReviewers.ROLES: | |||
|
40 | raise ValueError('role is not one of %s', PullRequestReviewers.ROLES) | |||
36 |
|
41 | |||
37 | return { |
|
42 | return { | |
38 | 'user_id': user.user_id, |
|
43 | 'user_id': user.user_id, | |
39 | 'reasons': reasons or [], |
|
44 | 'reasons': reasons or [], | |
40 | 'rules': rules or [], |
|
45 | 'rules': rules or [], | |
|
46 | 'role': role, | |||
41 | 'mandatory': mandatory, |
|
47 | 'mandatory': mandatory, | |
42 | 'user_group': user_group, |
|
48 | 'user_group': user_group, | |
43 | 'username': user.username, |
|
49 | 'username': user.username, | |
@@ -48,8 +54,7 b' def reviewer_as_json(user, reasons=None,' | |||||
48 | } |
|
54 | } | |
49 |
|
55 | |||
50 |
|
56 | |||
51 | def get_default_reviewers_data( |
|
57 | def get_default_reviewers_data(current_user, source_repo, source_commit, target_repo, target_commit): | |
52 | current_user, source_repo, source_commit, target_repo, target_commit): |
|
|||
53 | """ |
|
58 | """ | |
54 | Return json for default reviewers of a repository |
|
59 | Return json for default reviewers of a repository | |
55 | """ |
|
60 | """ | |
@@ -59,7 +64,7 b' def get_default_reviewers_data(' | |||||
59 |
|
64 | |||
60 | reasons = ['Default reviewer', 'Repository owner'] |
|
65 | reasons = ['Default reviewer', 'Repository owner'] | |
61 | json_reviewers = [reviewer_as_json( |
|
66 | json_reviewers = [reviewer_as_json( | |
62 | user=target_repo.user, reasons=reasons, mandatory=False, rules=None)] |
|
67 | user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)] | |
63 |
|
68 | |||
64 | return { |
|
69 | return { | |
65 | 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade |
|
70 | 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade | |
@@ -73,15 +78,18 b' def get_default_reviewers_data(' | |||||
73 | def validate_default_reviewers(review_members, reviewer_rules): |
|
78 | def validate_default_reviewers(review_members, reviewer_rules): | |
74 | """ |
|
79 | """ | |
75 | Function to validate submitted reviewers against the saved rules |
|
80 | Function to validate submitted reviewers against the saved rules | |
76 |
|
||||
77 | """ |
|
81 | """ | |
78 | reviewers = [] |
|
82 | reviewers = [] | |
79 | reviewer_by_id = {} |
|
83 | reviewer_by_id = {} | |
80 | for r in review_members: |
|
84 | for r in review_members: | |
81 | reviewer_user_id = safe_int(r['user_id']) |
|
85 | reviewer_user_id = safe_int(r['user_id']) | |
82 | entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['rules']) |
|
86 | entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules']) | |
83 |
|
87 | |||
84 | reviewer_by_id[reviewer_user_id] = entry |
|
88 | reviewer_by_id[reviewer_user_id] = entry | |
85 | reviewers.append(entry) |
|
89 | reviewers.append(entry) | |
86 |
|
90 | |||
87 | return reviewers |
|
91 | return reviewers | |
|
92 | ||||
|
93 | ||||
|
94 | def validate_observers(observer_members): | |||
|
95 | return {} |
@@ -193,7 +193,7 b' class RepoCommitsView(RepoAppView):' | |||||
193 |
|
193 | |||
194 | for review_obj, member, reasons, mandatory, status in review_statuses: |
|
194 | for review_obj, member, reasons, mandatory, status in review_statuses: | |
195 | member_reviewer = h.reviewer_as_json( |
|
195 | member_reviewer = h.reviewer_as_json( | |
196 | member, reasons=reasons, mandatory=mandatory, |
|
196 | member, reasons=reasons, mandatory=mandatory, role=None, | |
197 | user_group=None |
|
197 | user_group=None | |
198 | ) |
|
198 | ) | |
199 |
|
199 |
@@ -39,14 +39,15 b' from rhodecode.lib.ext_json import json' | |||||
39 | from rhodecode.lib.auth import ( |
|
39 | from rhodecode.lib.auth import ( | |
40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, |
|
40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, | |
41 | NotAnonymous, CSRFRequired) |
|
41 | NotAnonymous, CSRFRequired) | |
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int |
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist | |
43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason | |
44 | from rhodecode.lib.vcs.exceptions import ( |
|
44 | from rhodecode.lib.vcs.exceptions import ( | |
45 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) |
|
45 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) | |
46 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
46 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
47 | from rhodecode.model.comment import CommentsModel |
|
47 | from rhodecode.model.comment import CommentsModel | |
48 | from rhodecode.model.db import ( |
|
48 | from rhodecode.model.db import ( | |
49 |
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository |
|
49 | func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, | |
|
50 | PullRequestReviewers) | |||
50 | from rhodecode.model.forms import PullRequestForm |
|
51 | from rhodecode.model.forms import PullRequestForm | |
51 | from rhodecode.model.meta import Session |
|
52 | from rhodecode.model.meta import Session | |
52 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
|
53 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck | |
@@ -455,14 +456,18 b' class RepoPullRequestsView(RepoAppView, ' | |||||
455 | return self._get_template_context(c) |
|
456 | return self._get_template_context(c) | |
456 |
|
457 | |||
457 | c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user] |
|
458 | c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user] | |
|
459 | c.reviewers_count = pull_request.reviewers_count | |||
|
460 | c.observers_count = pull_request.observers_count | |||
458 |
|
461 | |||
459 | # reviewers and statuses |
|
462 | # reviewers and statuses | |
460 | c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data) |
|
463 | c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data) | |
461 | c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) |
|
464 | c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) | |
|
465 | c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []}) | |||
462 |
|
466 | |||
463 | for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses(): |
|
467 | for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses(): | |
464 | member_reviewer = h.reviewer_as_json( |
|
468 | member_reviewer = h.reviewer_as_json( | |
465 | member, reasons=reasons, mandatory=mandatory, |
|
469 | member, reasons=reasons, mandatory=mandatory, | |
|
470 | role=review_obj.role, | |||
466 | user_group=review_obj.rule_user_group_data() |
|
471 | user_group=review_obj.rule_user_group_data() | |
467 | ) |
|
472 | ) | |
468 |
|
473 | |||
@@ -474,6 +479,17 b' class RepoPullRequestsView(RepoAppView, ' | |||||
474 |
|
479 | |||
475 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) |
|
480 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) | |
476 |
|
481 | |||
|
482 | for observer_obj, member in pull_request_at_ver.observers(): | |||
|
483 | member_observer = h.reviewer_as_json( | |||
|
484 | member, reasons=[], mandatory=False, | |||
|
485 | role=observer_obj.role, | |||
|
486 | user_group=observer_obj.rule_user_group_data() | |||
|
487 | ) | |||
|
488 | member_observer['allowed_to_update'] = c.allowed_to_update | |||
|
489 | c.pull_request_set_observers_data_json['observers'].append(member_observer) | |||
|
490 | ||||
|
491 | c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json) | |||
|
492 | ||||
477 | general_comments, inline_comments = \ |
|
493 | general_comments, inline_comments = \ | |
478 | self.register_comments_vars(c, pull_request_latest, versions) |
|
494 | self.register_comments_vars(c, pull_request_latest, versions) | |
479 |
|
495 | |||
@@ -967,7 +983,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
967 | 'repository.read', 'repository.write', 'repository.admin') |
|
983 | 'repository.read', 'repository.write', 'repository.admin') | |
968 | @view_config( |
|
984 | @view_config( | |
969 | route_name='pullrequest_comments', request_method='POST', |
|
985 | route_name='pullrequest_comments', request_method='POST', | |
970 | renderer='string', xhr=True) |
|
986 | renderer='string_html', xhr=True) | |
971 | def pullrequest_comments(self): |
|
987 | def pullrequest_comments(self): | |
972 | self.load_default_context() |
|
988 | self.load_default_context() | |
973 |
|
989 | |||
@@ -998,7 +1014,8 b' class RepoPullRequestsView(RepoAppView, ' | |||||
998 | all_comments = c.inline_comments_flat + c.comments |
|
1014 | all_comments = c.inline_comments_flat + c.comments | |
999 |
|
1015 | |||
1000 | existing_ids = filter( |
|
1016 | existing_ids = filter( | |
1001 |
lambda e: e, map(safe_int, self.request.POST.get |
|
1017 | lambda e: e, map(safe_int, aslist(self.request.POST.get('comments')))) | |
|
1018 | ||||
1002 | return _render('comments_table', all_comments, len(all_comments), |
|
1019 | return _render('comments_table', all_comments, len(all_comments), | |
1003 | existing_ids=existing_ids) |
|
1020 | existing_ids=existing_ids) | |
1004 |
|
1021 | |||
@@ -1008,7 +1025,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1008 | 'repository.read', 'repository.write', 'repository.admin') |
|
1025 | 'repository.read', 'repository.write', 'repository.admin') | |
1009 | @view_config( |
|
1026 | @view_config( | |
1010 | route_name='pullrequest_todos', request_method='POST', |
|
1027 | route_name='pullrequest_todos', request_method='POST', | |
1011 | renderer='string', xhr=True) |
|
1028 | renderer='string_html', xhr=True) | |
1012 | def pullrequest_todos(self): |
|
1029 | def pullrequest_todos(self): | |
1013 | self.load_default_context() |
|
1030 | self.load_default_context() | |
1014 |
|
1031 | |||
@@ -1138,7 +1155,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1138 | target_ref_type, target_ref_name, __ = _form['target_ref'].split(':') |
|
1155 | target_ref_type, target_ref_name, __ = _form['target_ref'].split(':') | |
1139 | target_ref = ':'.join((target_ref_type, target_ref_name, ancestor)) |
|
1156 | target_ref = ':'.join((target_ref_type, target_ref_name, ancestor)) | |
1140 |
|
1157 | |||
1141 | get_default_reviewers_data, validate_default_reviewers = \ |
|
1158 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
1142 | PullRequestModel().get_reviewer_functions() |
|
1159 | PullRequestModel().get_reviewer_functions() | |
1143 |
|
1160 | |||
1144 | # recalculate reviewers logic, to make sure we can validate this |
|
1161 | # recalculate reviewers logic, to make sure we can validate this | |
@@ -1146,9 +1163,8 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1146 | self._rhodecode_db_user, source_db_repo, |
|
1163 | self._rhodecode_db_user, source_db_repo, | |
1147 | source_commit, target_db_repo, target_commit) |
|
1164 | source_commit, target_db_repo, target_commit) | |
1148 |
|
1165 | |||
1149 |
|
|
1166 | reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules) | |
1150 | reviewers = validate_default_reviewers( |
|
1167 | observers = validate_observers(_form['observer_members'], reviewer_rules) | |
1151 | given_reviewers, reviewer_rules) |
|
|||
1152 |
|
1168 | |||
1153 | pullrequest_title = _form['pullrequest_title'] |
|
1169 | pullrequest_title = _form['pullrequest_title'] | |
1154 | title_source_ref = source_ref.split(':', 2)[1] |
|
1170 | title_source_ref = source_ref.split(':', 2)[1] | |
@@ -1172,6 +1188,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1172 | revisions=commit_ids, |
|
1188 | revisions=commit_ids, | |
1173 | common_ancestor_id=common_ancestor_id, |
|
1189 | common_ancestor_id=common_ancestor_id, | |
1174 | reviewers=reviewers, |
|
1190 | reviewers=reviewers, | |
|
1191 | observers=observers, | |||
1175 | title=pullrequest_title, |
|
1192 | title=pullrequest_title, | |
1176 | description=description, |
|
1193 | description=description, | |
1177 | description_renderer=description_renderer, |
|
1194 | description_renderer=description_renderer, | |
@@ -1227,14 +1244,23 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1227 | # only owner or admin can update it |
|
1244 | # only owner or admin can update it | |
1228 | allowed_to_update = PullRequestModel().check_user_update( |
|
1245 | allowed_to_update = PullRequestModel().check_user_update( | |
1229 | pull_request, self._rhodecode_user) |
|
1246 | pull_request, self._rhodecode_user) | |
|
1247 | ||||
1230 | if allowed_to_update: |
|
1248 | if allowed_to_update: | |
1231 | controls = peppercorn.parse(self.request.POST.items()) |
|
1249 | controls = peppercorn.parse(self.request.POST.items()) | |
1232 | force_refresh = str2bool(self.request.POST.get('force_refresh')) |
|
1250 | force_refresh = str2bool(self.request.POST.get('force_refresh')) | |
1233 |
|
1251 | |||
1234 | if 'review_members' in controls: |
|
1252 | if 'review_members' in controls: | |
1235 | self._update_reviewers( |
|
1253 | self._update_reviewers( | |
|
1254 | c, | |||
1236 | pull_request, controls['review_members'], |
|
1255 | pull_request, controls['review_members'], | |
1237 |
pull_request.reviewer_data |
|
1256 | pull_request.reviewer_data, | |
|
1257 | PullRequestReviewers.ROLE_REVIEWER) | |||
|
1258 | elif 'observer_members' in controls: | |||
|
1259 | self._update_reviewers( | |||
|
1260 | c, | |||
|
1261 | pull_request, controls['observer_members'], | |||
|
1262 | pull_request.reviewer_data, | |||
|
1263 | PullRequestReviewers.ROLE_OBSERVER) | |||
1238 | elif str2bool(self.request.POST.get('update_commits', 'false')): |
|
1264 | elif str2bool(self.request.POST.get('update_commits', 'false')): | |
1239 | if is_state_changing: |
|
1265 | if is_state_changing: | |
1240 | log.debug('commits update: forbidden because pull request is in state %s', |
|
1266 | log.debug('commits update: forbidden because pull request is in state %s', | |
@@ -1255,6 +1281,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1255 | elif str2bool(self.request.POST.get('edit_pull_request', 'false')): |
|
1281 | elif str2bool(self.request.POST.get('edit_pull_request', 'false')): | |
1256 | self._edit_pull_request(pull_request) |
|
1282 | self._edit_pull_request(pull_request) | |
1257 | else: |
|
1283 | else: | |
|
1284 | log.error('Unhandled update data.') | |||
1258 | raise HTTPBadRequest() |
|
1285 | raise HTTPBadRequest() | |
1259 |
|
1286 | |||
1260 | return {'response': True, |
|
1287 | return {'response': True, | |
@@ -1262,6 +1289,9 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1262 | raise HTTPForbidden() |
|
1289 | raise HTTPForbidden() | |
1263 |
|
1290 | |||
1264 | def _edit_pull_request(self, pull_request): |
|
1291 | def _edit_pull_request(self, pull_request): | |
|
1292 | """ | |||
|
1293 | Edit title and description | |||
|
1294 | """ | |||
1265 | _ = self.request.translate |
|
1295 | _ = self.request.translate | |
1266 |
|
1296 | |||
1267 | try: |
|
1297 | try: | |
@@ -1302,27 +1332,14 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1302 |
|
1332 | |||
1303 | msg = _(u'Pull request updated to "{source_commit_id}" with ' |
|
1333 | msg = _(u'Pull request updated to "{source_commit_id}" with ' | |
1304 | u'{count_added} added, {count_removed} removed commits. ' |
|
1334 | u'{count_added} added, {count_removed} removed commits. ' | |
1305 | u'Source of changes: {change_source}') |
|
1335 | u'Source of changes: {change_source}.') | |
1306 | msg = msg.format( |
|
1336 | msg = msg.format( | |
1307 | source_commit_id=pull_request.source_ref_parts.commit_id, |
|
1337 | source_commit_id=pull_request.source_ref_parts.commit_id, | |
1308 | count_added=len(resp.changes.added), |
|
1338 | count_added=len(resp.changes.added), | |
1309 | count_removed=len(resp.changes.removed), |
|
1339 | count_removed=len(resp.changes.removed), | |
1310 | change_source=changed) |
|
1340 | change_source=changed) | |
1311 | h.flash(msg, category='success') |
|
1341 | h.flash(msg, category='success') | |
1312 |
|
1342 | self._pr_update_channelstream_push(c.pr_broadcast_channel, msg) | ||
1313 | message = msg + ( |
|
|||
1314 | ' - <a onclick="window.location.reload()">' |
|
|||
1315 | '<strong>{}</strong></a>'.format(_('Reload page'))) |
|
|||
1316 |
|
||||
1317 | message_obj = { |
|
|||
1318 | 'message': message, |
|
|||
1319 | 'level': 'success', |
|
|||
1320 | 'topic': '/notifications' |
|
|||
1321 | } |
|
|||
1322 |
|
||||
1323 | channelstream.post_message( |
|
|||
1324 | c.pr_broadcast_channel, message_obj, self._rhodecode_user.username, |
|
|||
1325 | registry=self.request.registry) |
|
|||
1326 | else: |
|
1343 | else: | |
1327 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] |
|
1344 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] | |
1328 | warning_reasons = [ |
|
1345 | warning_reasons = [ | |
@@ -1332,6 +1349,53 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1332 | category = 'warning' if resp.reason in warning_reasons else 'error' |
|
1349 | category = 'warning' if resp.reason in warning_reasons else 'error' | |
1333 | h.flash(msg, category=category) |
|
1350 | h.flash(msg, category=category) | |
1334 |
|
1351 | |||
|
1352 | def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role): | |||
|
1353 | _ = self.request.translate | |||
|
1354 | ||||
|
1355 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |||
|
1356 | PullRequestModel().get_reviewer_functions() | |||
|
1357 | ||||
|
1358 | if role == PullRequestReviewers.ROLE_REVIEWER: | |||
|
1359 | try: | |||
|
1360 | reviewers = validate_default_reviewers(review_members, reviewer_rules) | |||
|
1361 | except ValueError as e: | |||
|
1362 | log.error('Reviewers Validation: {}'.format(e)) | |||
|
1363 | h.flash(e, category='error') | |||
|
1364 | return | |||
|
1365 | ||||
|
1366 | old_calculated_status = pull_request.calculated_review_status() | |||
|
1367 | PullRequestModel().update_reviewers( | |||
|
1368 | pull_request, reviewers, self._rhodecode_user) | |||
|
1369 | ||||
|
1370 | Session().commit() | |||
|
1371 | ||||
|
1372 | msg = _('Pull request reviewers updated.') | |||
|
1373 | h.flash(msg, category='success') | |||
|
1374 | self._pr_update_channelstream_push(c.pr_broadcast_channel, msg) | |||
|
1375 | ||||
|
1376 | # trigger status changed if change in reviewers changes the status | |||
|
1377 | calculated_status = pull_request.calculated_review_status() | |||
|
1378 | if old_calculated_status != calculated_status: | |||
|
1379 | PullRequestModel().trigger_pull_request_hook( | |||
|
1380 | pull_request, self._rhodecode_user, 'review_status_change', | |||
|
1381 | data={'status': calculated_status}) | |||
|
1382 | ||||
|
1383 | elif role == PullRequestReviewers.ROLE_OBSERVER: | |||
|
1384 | try: | |||
|
1385 | observers = validate_observers(review_members, reviewer_rules) | |||
|
1386 | except ValueError as e: | |||
|
1387 | log.error('Observers Validation: {}'.format(e)) | |||
|
1388 | h.flash(e, category='error') | |||
|
1389 | return | |||
|
1390 | ||||
|
1391 | PullRequestModel().update_observers( | |||
|
1392 | pull_request, observers, self._rhodecode_user) | |||
|
1393 | ||||
|
1394 | Session().commit() | |||
|
1395 | msg = _('Pull request observers updated.') | |||
|
1396 | h.flash(msg, category='success') | |||
|
1397 | self._pr_update_channelstream_push(c.pr_broadcast_channel, msg) | |||
|
1398 | ||||
1335 | @LoginRequired() |
|
1399 | @LoginRequired() | |
1336 | @NotAnonymous() |
|
1400 | @NotAnonymous() | |
1337 | @HasRepoPermissionAnyDecorator( |
|
1401 | @HasRepoPermissionAnyDecorator( | |
@@ -1408,32 +1472,6 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1408 | msg = merge_resp.merge_status_message |
|
1472 | msg = merge_resp.merge_status_message | |
1409 | h.flash(msg, category='error') |
|
1473 | h.flash(msg, category='error') | |
1410 |
|
1474 | |||
1411 | def _update_reviewers(self, pull_request, review_members, reviewer_rules): |
|
|||
1412 | _ = self.request.translate |
|
|||
1413 |
|
||||
1414 | get_default_reviewers_data, validate_default_reviewers = \ |
|
|||
1415 | PullRequestModel().get_reviewer_functions() |
|
|||
1416 |
|
||||
1417 | try: |
|
|||
1418 | reviewers = validate_default_reviewers(review_members, reviewer_rules) |
|
|||
1419 | except ValueError as e: |
|
|||
1420 | log.error('Reviewers Validation: {}'.format(e)) |
|
|||
1421 | h.flash(e, category='error') |
|
|||
1422 | return |
|
|||
1423 |
|
||||
1424 | old_calculated_status = pull_request.calculated_review_status() |
|
|||
1425 | PullRequestModel().update_reviewers( |
|
|||
1426 | pull_request, reviewers, self._rhodecode_user) |
|
|||
1427 | h.flash(_('Pull request reviewers updated.'), category='success') |
|
|||
1428 | Session().commit() |
|
|||
1429 |
|
||||
1430 | # trigger status changed if change in reviewers changes the status |
|
|||
1431 | calculated_status = pull_request.calculated_review_status() |
|
|||
1432 | if old_calculated_status != calculated_status: |
|
|||
1433 | PullRequestModel().trigger_pull_request_hook( |
|
|||
1434 | pull_request, self._rhodecode_user, 'review_status_change', |
|
|||
1435 | data={'status': calculated_status}) |
|
|||
1436 |
|
||||
1437 | @LoginRequired() |
|
1475 | @LoginRequired() | |
1438 | @NotAnonymous() |
|
1476 | @NotAnonymous() | |
1439 | @HasRepoPermissionAnyDecorator( |
|
1477 | @HasRepoPermissionAnyDecorator( | |
@@ -1488,8 +1526,7 b' class RepoPullRequestsView(RepoAppView, ' | |||||
1488 | allowed_to_comment = PullRequestModel().check_user_comment( |
|
1526 | allowed_to_comment = PullRequestModel().check_user_comment( | |
1489 | pull_request, self._rhodecode_user) |
|
1527 | pull_request, self._rhodecode_user) | |
1490 | if not allowed_to_comment: |
|
1528 | if not allowed_to_comment: | |
1491 | log.debug( |
|
1529 | log.debug('comment: forbidden because pull request is from forbidden repo') | |
1492 | 'comment: forbidden because pull request is from forbidden repo') |
|
|||
1493 | raise HTTPForbidden() |
|
1530 | raise HTTPForbidden() | |
1494 |
|
1531 | |||
1495 | c = self.load_default_context() |
|
1532 | c = self.load_default_context() |
@@ -341,6 +341,10 b' def includeme(config):' | |||||
341 | name='json_ext', |
|
341 | name='json_ext', | |
342 | factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') |
|
342 | factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') | |
343 |
|
343 | |||
|
344 | config.add_renderer( | |||
|
345 | name='string_html', | |||
|
346 | factory='rhodecode.lib.string_renderer.html') | |||
|
347 | ||||
344 | # include RhodeCode plugins |
|
348 | # include RhodeCode plugins | |
345 | includes = aslist(settings.get('rhodecode.includes', [])) |
|
349 | includes = aslist(settings.get('rhodecode.includes', [])) | |
346 | for inc in includes: |
|
350 | for inc in includes: |
@@ -88,6 +88,9 b' ACTIONS_V1 = {' | |||||
88 | 'repo.pull_request.reviewer.add': '', |
|
88 | 'repo.pull_request.reviewer.add': '', | |
89 | 'repo.pull_request.reviewer.delete': '', |
|
89 | 'repo.pull_request.reviewer.delete': '', | |
90 |
|
90 | |||
|
91 | 'repo.pull_request.observer.add': '', | |||
|
92 | 'repo.pull_request.observer.delete': '', | |||
|
93 | ||||
91 | 'repo.commit.strip': {'commit_id': ''}, |
|
94 | 'repo.commit.strip': {'commit_id': ''}, | |
92 | 'repo.commit.comment.create': {'data': {}}, |
|
95 | 'repo.commit.comment.create': {'data': {}}, | |
93 | 'repo.commit.comment.delete': {'data': {}}, |
|
96 | 'repo.commit.comment.delete': {'data': {}}, |
@@ -1104,6 +1104,10 b' def bool2icon(value, show_at_false=True)' | |||||
1104 | return HTML.tag('i', class_="icon-false", title='False') |
|
1104 | return HTML.tag('i', class_="icon-false", title='False') | |
1105 | return HTML.tag('i') |
|
1105 | return HTML.tag('i') | |
1106 |
|
1106 | |||
|
1107 | ||||
|
1108 | def b64(inp): | |||
|
1109 | return base64.b64encode(inp) | |||
|
1110 | ||||
1107 | #============================================================================== |
|
1111 | #============================================================================== | |
1108 | # PERMS |
|
1112 | # PERMS | |
1109 | #============================================================================== |
|
1113 | #============================================================================== |
@@ -25,7 +25,7 b' import collections' | |||||
25 |
|
25 | |||
26 | from rhodecode.model import BaseModel |
|
26 | from rhodecode.model import BaseModel | |
27 | from rhodecode.model.db import ( |
|
27 | from rhodecode.model.db import ( | |
28 | ChangesetStatus, ChangesetComment, PullRequest, Session) |
|
28 | ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers, Session) | |
29 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
29 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError | |
30 | from rhodecode.lib.markup_renderer import ( |
|
30 | from rhodecode.lib.markup_renderer import ( | |
31 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
|
31 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) | |
@@ -383,15 +383,14 b' class ChangesetStatusModel(BaseModel):' | |||||
383 | pull_request.source_repo, |
|
383 | pull_request.source_repo, | |
384 | pull_request=pull_request, |
|
384 | pull_request=pull_request, | |
385 | with_revisions=True) |
|
385 | with_revisions=True) | |
|
386 | reviewers = pull_request.get_pull_request_reviewers( | |||
|
387 | role=PullRequestReviewers.ROLE_REVIEWER) | |||
|
388 | return self.aggregate_votes_by_user(_commit_statuses, reviewers) | |||
386 |
|
389 | |||
387 | return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) |
|
390 | def calculated_review_status(self, pull_request): | |
388 |
|
||||
389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): |
|
|||
390 | """ |
|
391 | """ | |
391 | calculate pull request status based on reviewers, it should be a list |
|
392 | calculate pull request status based on reviewers, it should be a list | |
392 | of two element lists. |
|
393 | of two element lists. | |
393 |
|
||||
394 | :param reviewers_statuses: |
|
|||
395 | """ |
|
394 | """ | |
396 |
reviewers = |
|
395 | reviewers = self.reviewers_statuses(pull_request) | |
397 | return self.calculate_status(reviewers) |
|
396 | return self.calculate_status(reviewers) |
@@ -436,9 +436,8 b' class CommentsModel(BaseModel):' | |||||
436 | 'thread_ids': [pr_url, pr_comment_url], |
|
436 | 'thread_ids': [pr_url, pr_comment_url], | |
437 | }) |
|
437 | }) | |
438 |
|
438 | |||
|
439 | if send_email: | |||
439 | recipients += [self._get_user(u) for u in (extra_recipients or [])] |
|
440 | recipients += [self._get_user(u) for u in (extra_recipients or [])] | |
440 |
|
||||
441 | if send_email: |
|
|||
442 | # pre-generate the subject for notification itself |
|
441 | # pre-generate the subject for notification itself | |
443 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
442 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( | |
444 | notification_type, **kwargs) |
|
443 | notification_type, **kwargs) |
@@ -4465,6 +4465,37 b' class PullRequest(Base, _PullRequestBase' | |||||
4465 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
4465 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
4466 | return ChangesetStatusModel().reviewers_statuses(self) |
|
4466 | return ChangesetStatusModel().reviewers_statuses(self) | |
4467 |
|
4467 | |||
|
4468 | def get_pull_request_reviewers(self, role=None): | |||
|
4469 | qry = PullRequestReviewers.query()\ | |||
|
4470 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id) | |||
|
4471 | if role: | |||
|
4472 | qry = qry.filter(PullRequestReviewers.role == role) | |||
|
4473 | ||||
|
4474 | return qry.all() | |||
|
4475 | ||||
|
4476 | @property | |||
|
4477 | def reviewers_count(self): | |||
|
4478 | qry = PullRequestReviewers.query()\ | |||
|
4479 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |||
|
4480 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER) | |||
|
4481 | return qry.count() | |||
|
4482 | ||||
|
4483 | @property | |||
|
4484 | def observers_count(self): | |||
|
4485 | qry = PullRequestReviewers.query()\ | |||
|
4486 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |||
|
4487 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER) | |||
|
4488 | return qry.count() | |||
|
4489 | ||||
|
4490 | def observers(self): | |||
|
4491 | qry = PullRequestReviewers.query()\ | |||
|
4492 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |||
|
4493 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\ | |||
|
4494 | .all() | |||
|
4495 | ||||
|
4496 | for entry in qry: | |||
|
4497 | yield entry, entry.user | |||
|
4498 | ||||
4468 | @property |
|
4499 | @property | |
4469 | def workspace_id(self): |
|
4500 | def workspace_id(self): | |
4470 | from rhodecode.model.pull_request import PullRequestModel |
|
4501 | from rhodecode.model.pull_request import PullRequestModel | |
@@ -4530,6 +4561,9 b' class PullRequestVersion(Base, _PullRequ' | |||||
4530 | def reviewers_statuses(self): |
|
4561 | def reviewers_statuses(self): | |
4531 | return self.pull_request.reviewers_statuses() |
|
4562 | return self.pull_request.reviewers_statuses() | |
4532 |
|
4563 | |||
|
4564 | def observer(self): | |||
|
4565 | return self.pull_request.observers() | |||
|
4566 | ||||
4533 |
|
4567 | |||
4534 | class PullRequestReviewers(Base, BaseModel): |
|
4568 | class PullRequestReviewers(Base, BaseModel): | |
4535 | __tablename__ = 'pull_request_reviewers' |
|
4569 | __tablename__ = 'pull_request_reviewers' | |
@@ -4538,6 +4572,7 b' class PullRequestReviewers(Base, BaseMod' | |||||
4538 | ) |
|
4572 | ) | |
4539 | ROLE_REVIEWER = u'reviewer' |
|
4573 | ROLE_REVIEWER = u'reviewer' | |
4540 | ROLE_OBSERVER = u'observer' |
|
4574 | ROLE_OBSERVER = u'observer' | |
|
4575 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |||
4541 |
|
4576 | |||
4542 | @hybrid_property |
|
4577 | @hybrid_property | |
4543 | def reasons(self): |
|
4578 | def reasons(self): | |
@@ -4589,6 +4624,15 b' class PullRequestReviewers(Base, BaseMod' | |||||
4589 |
|
4624 | |||
4590 | return user_group_data |
|
4625 | return user_group_data | |
4591 |
|
4626 | |||
|
4627 | @classmethod | |||
|
4628 | def get_pull_request_reviewers(cls, pull_request_id, role=None): | |||
|
4629 | qry = PullRequestReviewers.query()\ | |||
|
4630 | .filter(PullRequestReviewers.pull_request_id == pull_request_id) | |||
|
4631 | if role: | |||
|
4632 | qry = qry.filter(PullRequestReviewers.role == role) | |||
|
4633 | ||||
|
4634 | return qry.all() | |||
|
4635 | ||||
4592 | def __unicode__(self): |
|
4636 | def __unicode__(self): | |
4593 | return u"<%s('id:%s')>" % (self.__class__.__name__, |
|
4637 | return u"<%s('id:%s')>" % (self.__class__.__name__, | |
4594 | self.pull_requests_reviewers_id) |
|
4638 | self.pull_requests_reviewers_id) | |
@@ -4954,16 +4998,21 b' class RepoReviewRuleUser(Base, BaseModel' | |||||
4954 | __table_args__ = ( |
|
4998 | __table_args__ = ( | |
4955 | base_table_args |
|
4999 | base_table_args | |
4956 | ) |
|
5000 | ) | |
|
5001 | ROLE_REVIEWER = u'reviewer' | |||
|
5002 | ROLE_OBSERVER = u'observer' | |||
|
5003 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |||
4957 |
|
5004 | |||
4958 | repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True) |
|
5005 | repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True) | |
4959 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) |
|
5006 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) | |
4960 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False) |
|
5007 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False) | |
4961 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) |
|
5008 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) | |
|
5009 | role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER) | |||
4962 | user = relationship('User') |
|
5010 | user = relationship('User') | |
4963 |
|
5011 | |||
4964 | def rule_data(self): |
|
5012 | def rule_data(self): | |
4965 | return { |
|
5013 | return { | |
4966 | 'mandatory': self.mandatory |
|
5014 | 'mandatory': self.mandatory, | |
|
5015 | 'role': self.role, | |||
4967 | } |
|
5016 | } | |
4968 |
|
5017 | |||
4969 |
|
5018 | |||
@@ -4974,17 +5023,22 b' class RepoReviewRuleUserGroup(Base, Base' | |||||
4974 | ) |
|
5023 | ) | |
4975 |
|
5024 | |||
4976 | VOTE_RULE_ALL = -1 |
|
5025 | VOTE_RULE_ALL = -1 | |
|
5026 | ROLE_REVIEWER = u'reviewer' | |||
|
5027 | ROLE_OBSERVER = u'observer' | |||
|
5028 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |||
4977 |
|
5029 | |||
4978 | repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True) |
|
5030 | repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True) | |
4979 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) |
|
5031 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) | |
4980 | users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False) |
|
5032 | users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False) | |
4981 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) |
|
5033 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) | |
|
5034 | role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER) | |||
4982 | vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL) |
|
5035 | vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL) | |
4983 | users_group = relationship('UserGroup') |
|
5036 | users_group = relationship('UserGroup') | |
4984 |
|
5037 | |||
4985 | def rule_data(self): |
|
5038 | def rule_data(self): | |
4986 | return { |
|
5039 | return { | |
4987 | 'mandatory': self.mandatory, |
|
5040 | 'mandatory': self.mandatory, | |
|
5041 | 'role': self.role, | |||
4988 | 'vote_rule': self.vote_rule |
|
5042 | 'vote_rule': self.vote_rule | |
4989 | } |
|
5043 | } | |
4990 |
|
5044 |
@@ -601,6 +601,14 b' def PullRequestForm(localizer, repo_id):' | |||||
601 | reasons = All() |
|
601 | reasons = All() | |
602 | rules = All(v.UniqueList(localizer, convert=int)()) |
|
602 | rules = All(v.UniqueList(localizer, convert=int)()) | |
603 | mandatory = v.StringBoolean() |
|
603 | mandatory = v.StringBoolean() | |
|
604 | role = v.String(if_missing='reviewer') | |||
|
605 | ||||
|
606 | class ObserverForm(formencode.Schema): | |||
|
607 | user_id = v.Int(not_empty=True) | |||
|
608 | reasons = All() | |||
|
609 | rules = All(v.UniqueList(localizer, convert=int)()) | |||
|
610 | mandatory = v.StringBoolean() | |||
|
611 | role = v.String(if_missing='observer') | |||
604 |
|
612 | |||
605 | class _PullRequestForm(formencode.Schema): |
|
613 | class _PullRequestForm(formencode.Schema): | |
606 | allow_extra_fields = True |
|
614 | allow_extra_fields = True | |
@@ -614,6 +622,7 b' def PullRequestForm(localizer, repo_id):' | |||||
614 | revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(), |
|
622 | revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(), | |
615 | v.UniqueList(localizer)(not_empty=True)) |
|
623 | v.UniqueList(localizer)(not_empty=True)) | |
616 | review_members = formencode.ForEach(ReviewerForm()) |
|
624 | review_members = formencode.ForEach(ReviewerForm()) | |
|
625 | observer_members = formencode.ForEach(ObserverForm()) | |||
617 | pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255) |
|
626 | pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255) | |
618 | pullrequest_desc = v.UnicodeString(strip=True, required=False) |
|
627 | pullrequest_desc = v.UnicodeString(strip=True, required=False) | |
619 | description_renderer = v.UnicodeString(strip=True, required=False) |
|
628 | description_renderer = v.UnicodeString(strip=True, required=False) |
@@ -575,7 +575,7 b' class PullRequestModel(BaseModel):' | |||||
575 | pull_request_display_obj, at_version |
|
575 | pull_request_display_obj, at_version | |
576 |
|
576 | |||
577 | def create(self, created_by, source_repo, source_ref, target_repo, |
|
577 | def create(self, created_by, source_repo, source_ref, target_repo, | |
578 | target_ref, revisions, reviewers, title, description=None, |
|
578 | target_ref, revisions, reviewers, observers, title, description=None, | |
579 | common_ancestor_id=None, |
|
579 | common_ancestor_id=None, | |
580 | description_renderer=None, |
|
580 | description_renderer=None, | |
581 | reviewer_data=None, translator=None, auth_user=None): |
|
581 | reviewer_data=None, translator=None, auth_user=None): | |
@@ -606,7 +606,7 b' class PullRequestModel(BaseModel):' | |||||
606 | reviewer_ids = set() |
|
606 | reviewer_ids = set() | |
607 | # members / reviewers |
|
607 | # members / reviewers | |
608 | for reviewer_object in reviewers: |
|
608 | for reviewer_object in reviewers: | |
609 | user_id, reasons, mandatory, rules = reviewer_object |
|
609 | user_id, reasons, mandatory, role, rules = reviewer_object | |
610 | user = self._get_user(user_id) |
|
610 | user = self._get_user(user_id) | |
611 |
|
611 | |||
612 | # skip duplicates |
|
612 | # skip duplicates | |
@@ -620,6 +620,7 b' class PullRequestModel(BaseModel):' | |||||
620 | reviewer.pull_request = pull_request |
|
620 | reviewer.pull_request = pull_request | |
621 | reviewer.reasons = reasons |
|
621 | reviewer.reasons = reasons | |
622 | reviewer.mandatory = mandatory |
|
622 | reviewer.mandatory = mandatory | |
|
623 | reviewer.role = role | |||
623 |
|
624 | |||
624 | # NOTE(marcink): pick only first rule for now |
|
625 | # NOTE(marcink): pick only first rule for now | |
625 | rule_id = list(rules)[0] if rules else None |
|
626 | rule_id = list(rules)[0] if rules else None | |
@@ -653,6 +654,33 b' class PullRequestModel(BaseModel):' | |||||
653 | Session().add(reviewer) |
|
654 | Session().add(reviewer) | |
654 | Session().flush() |
|
655 | Session().flush() | |
655 |
|
656 | |||
|
657 | for observer_object in observers: | |||
|
658 | user_id, reasons, mandatory, role, rules = observer_object | |||
|
659 | user = self._get_user(user_id) | |||
|
660 | ||||
|
661 | # skip duplicates from reviewers | |||
|
662 | if user.user_id in reviewer_ids: | |||
|
663 | continue | |||
|
664 | ||||
|
665 | #reviewer_ids.add(user.user_id) | |||
|
666 | ||||
|
667 | observer = PullRequestReviewers() | |||
|
668 | observer.user = user | |||
|
669 | observer.pull_request = pull_request | |||
|
670 | observer.reasons = reasons | |||
|
671 | observer.mandatory = mandatory | |||
|
672 | observer.role = role | |||
|
673 | ||||
|
674 | # NOTE(marcink): pick only first rule for now | |||
|
675 | rule_id = list(rules)[0] if rules else None | |||
|
676 | rule = RepoReviewRule.get(rule_id) if rule_id else None | |||
|
677 | if rule: | |||
|
678 | # TODO(marcink): do we need this for observers ?? | |||
|
679 | pass | |||
|
680 | ||||
|
681 | Session().add(observer) | |||
|
682 | Session().flush() | |||
|
683 | ||||
656 | # Set approval status to "Under Review" for all commits which are |
|
684 | # Set approval status to "Under Review" for all commits which are | |
657 | # part of this pull request. |
|
685 | # part of this pull request. | |
658 | ChangesetStatusModel().set_status( |
|
686 | ChangesetStatusModel().set_status( | |
@@ -1204,23 +1232,25 b' class PullRequestModel(BaseModel):' | |||||
1204 |
|
1232 | |||
1205 | :param pull_request: the pr to update |
|
1233 | :param pull_request: the pr to update | |
1206 | :param reviewer_data: list of tuples |
|
1234 | :param reviewer_data: list of tuples | |
1207 | [(user, ['reason1', 'reason2'], mandatory_flag, [rules])] |
|
1235 | [(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])] | |
|
1236 | :param user: current use who triggers this action | |||
1208 | """ |
|
1237 | """ | |
|
1238 | ||||
1209 | pull_request = self.__get_pull_request(pull_request) |
|
1239 | pull_request = self.__get_pull_request(pull_request) | |
1210 | if pull_request.is_closed(): |
|
1240 | if pull_request.is_closed(): | |
1211 | raise ValueError('This pull request is closed') |
|
1241 | raise ValueError('This pull request is closed') | |
1212 |
|
1242 | |||
1213 | reviewers = {} |
|
1243 | reviewers = {} | |
1214 | for user_id, reasons, mandatory, rules in reviewer_data: |
|
1244 | for user_id, reasons, mandatory, role, rules in reviewer_data: | |
1215 | if isinstance(user_id, (int, compat.string_types)): |
|
1245 | if isinstance(user_id, (int, compat.string_types)): | |
1216 | user_id = self._get_user(user_id).user_id |
|
1246 | user_id = self._get_user(user_id).user_id | |
1217 | reviewers[user_id] = { |
|
1247 | reviewers[user_id] = { | |
1218 | 'reasons': reasons, 'mandatory': mandatory} |
|
1248 | 'reasons': reasons, 'mandatory': mandatory, 'role': role} | |
1219 |
|
1249 | |||
1220 | reviewers_ids = set(reviewers.keys()) |
|
1250 | reviewers_ids = set(reviewers.keys()) | |
1221 |
current_reviewers = PullRequestReviewers. |
|
1251 | current_reviewers = PullRequestReviewers.get_pull_request_reviewers( | |
1222 | .filter(PullRequestReviewers.pull_request == |
|
1252 | pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER) | |
1223 | pull_request).all() |
|
1253 | ||
1224 | current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) |
|
1254 | current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) | |
1225 |
|
1255 | |||
1226 | ids_to_add = reviewers_ids.difference(current_reviewers_ids) |
|
1256 | ids_to_add = reviewers_ids.difference(current_reviewers_ids) | |
@@ -1241,16 +1271,19 b' class PullRequestModel(BaseModel):' | |||||
1241 | reviewer.reasons = reviewers[uid]['reasons'] |
|
1271 | reviewer.reasons = reviewers[uid]['reasons'] | |
1242 | # NOTE(marcink): mandatory shouldn't be changed now |
|
1272 | # NOTE(marcink): mandatory shouldn't be changed now | |
1243 | # reviewer.mandatory = reviewers[uid]['reasons'] |
|
1273 | # reviewer.mandatory = reviewers[uid]['reasons'] | |
|
1274 | # NOTE(marcink): role should be hardcoded, so we won't edit it. | |||
|
1275 | reviewer.role = PullRequestReviewers.ROLE_REVIEWER | |||
1244 | Session().add(reviewer) |
|
1276 | Session().add(reviewer) | |
1245 | added_audit_reviewers.append(reviewer.get_dict()) |
|
1277 | added_audit_reviewers.append(reviewer.get_dict()) | |
1246 |
|
1278 | |||
1247 | for uid in ids_to_remove: |
|
1279 | for uid in ids_to_remove: | |
1248 | changed = True |
|
1280 | changed = True | |
1249 |
# NOTE(marcink): we fetch "ALL" reviewers using .all(). |
|
1281 | # NOTE(marcink): we fetch "ALL" reviewers objects using .all(). | |
1250 |
# |
|
1282 | # This is an edge case that handles previous state of having the same reviewer twice. | |
1251 | # this CAN happen due to the lack of DB checks |
|
1283 | # this CAN happen due to the lack of DB checks | |
1252 | reviewers = PullRequestReviewers.query()\ |
|
1284 | reviewers = PullRequestReviewers.query()\ | |
1253 | .filter(PullRequestReviewers.user_id == uid, |
|
1285 | .filter(PullRequestReviewers.user_id == uid, | |
|
1286 | PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER, | |||
1254 | PullRequestReviewers.pull_request == pull_request)\ |
|
1287 | PullRequestReviewers.pull_request == pull_request)\ | |
1255 | .all() |
|
1288 | .all() | |
1256 |
|
1289 | |||
@@ -1273,7 +1306,90 b' class PullRequestModel(BaseModel):' | |||||
1273 | 'repo.pull_request.reviewer.delete', {'old_data': user_data}, |
|
1306 | 'repo.pull_request.reviewer.delete', {'old_data': user_data}, | |
1274 | user, pull_request) |
|
1307 | user, pull_request) | |
1275 |
|
1308 | |||
1276 | self.notify_reviewers(pull_request, ids_to_add) |
|
1309 | self.notify_reviewers(pull_request, ids_to_add, user.get_instance()) | |
|
1310 | return ids_to_add, ids_to_remove | |||
|
1311 | ||||
|
1312 | def update_observers(self, pull_request, observer_data, user): | |||
|
1313 | """ | |||
|
1314 | Update the observers in the pull request | |||
|
1315 | ||||
|
1316 | :param pull_request: the pr to update | |||
|
1317 | :param observer_data: list of tuples | |||
|
1318 | [(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])] | |||
|
1319 | :param user: current use who triggers this action | |||
|
1320 | """ | |||
|
1321 | pull_request = self.__get_pull_request(pull_request) | |||
|
1322 | if pull_request.is_closed(): | |||
|
1323 | raise ValueError('This pull request is closed') | |||
|
1324 | ||||
|
1325 | observers = {} | |||
|
1326 | for user_id, reasons, mandatory, role, rules in observer_data: | |||
|
1327 | if isinstance(user_id, (int, compat.string_types)): | |||
|
1328 | user_id = self._get_user(user_id).user_id | |||
|
1329 | observers[user_id] = { | |||
|
1330 | 'reasons': reasons, 'observers': mandatory, 'role': role} | |||
|
1331 | ||||
|
1332 | observers_ids = set(observers.keys()) | |||
|
1333 | current_observers = PullRequestReviewers.get_pull_request_reviewers( | |||
|
1334 | pull_request.pull_request_id, role=PullRequestReviewers.ROLE_OBSERVER) | |||
|
1335 | ||||
|
1336 | current_observers_ids = set([x.user.user_id for x in current_observers]) | |||
|
1337 | ||||
|
1338 | ids_to_add = observers_ids.difference(current_observers_ids) | |||
|
1339 | ids_to_remove = current_observers_ids.difference(observers_ids) | |||
|
1340 | ||||
|
1341 | log.debug("Adding %s observer", ids_to_add) | |||
|
1342 | log.debug("Removing %s observer", ids_to_remove) | |||
|
1343 | changed = False | |||
|
1344 | added_audit_observers = [] | |||
|
1345 | removed_audit_observers = [] | |||
|
1346 | ||||
|
1347 | for uid in ids_to_add: | |||
|
1348 | changed = True | |||
|
1349 | _usr = self._get_user(uid) | |||
|
1350 | observer = PullRequestReviewers() | |||
|
1351 | observer.user = _usr | |||
|
1352 | observer.pull_request = pull_request | |||
|
1353 | observer.reasons = observers[uid]['reasons'] | |||
|
1354 | # NOTE(marcink): mandatory shouldn't be changed now | |||
|
1355 | # observer.mandatory = observer[uid]['reasons'] | |||
|
1356 | ||||
|
1357 | # NOTE(marcink): role should be hardcoded, so we won't edit it. | |||
|
1358 | observer.role = PullRequestReviewers.ROLE_OBSERVER | |||
|
1359 | Session().add(observer) | |||
|
1360 | added_audit_observers.append(observer.get_dict()) | |||
|
1361 | ||||
|
1362 | for uid in ids_to_remove: | |||
|
1363 | changed = True | |||
|
1364 | # NOTE(marcink): we fetch "ALL" reviewers objects using .all(). | |||
|
1365 | # This is an edge case that handles previous state of having the same reviewer twice. | |||
|
1366 | # this CAN happen due to the lack of DB checks | |||
|
1367 | observers = PullRequestReviewers.query()\ | |||
|
1368 | .filter(PullRequestReviewers.user_id == uid, | |||
|
1369 | PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER, | |||
|
1370 | PullRequestReviewers.pull_request == pull_request)\ | |||
|
1371 | .all() | |||
|
1372 | ||||
|
1373 | for obj in observers: | |||
|
1374 | added_audit_observers.append(obj.get_dict()) | |||
|
1375 | Session().delete(obj) | |||
|
1376 | ||||
|
1377 | if changed: | |||
|
1378 | Session().expire_all() | |||
|
1379 | pull_request.updated_on = datetime.datetime.now() | |||
|
1380 | Session().add(pull_request) | |||
|
1381 | ||||
|
1382 | # finally store audit logs | |||
|
1383 | for user_data in added_audit_observers: | |||
|
1384 | self._log_audit_action( | |||
|
1385 | 'repo.pull_request.observer.add', {'data': user_data}, | |||
|
1386 | user, pull_request) | |||
|
1387 | for user_data in removed_audit_observers: | |||
|
1388 | self._log_audit_action( | |||
|
1389 | 'repo.pull_request.observer.delete', {'old_data': user_data}, | |||
|
1390 | user, pull_request) | |||
|
1391 | ||||
|
1392 | self.notify_observers(pull_request, ids_to_add, user.get_instance()) | |||
1277 | return ids_to_add, ids_to_remove |
|
1393 | return ids_to_add, ids_to_remove | |
1278 |
|
1394 | |||
1279 | def get_url(self, pull_request, request=None, permalink=False): |
|
1395 | def get_url(self, pull_request, request=None, permalink=False): | |
@@ -1301,16 +1417,16 b' class PullRequestModel(BaseModel):' | |||||
1301 | pr_url = urllib.unquote(self.get_url(pull_request, request=request)) |
|
1417 | pr_url = urllib.unquote(self.get_url(pull_request, request=request)) | |
1302 | return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url)) |
|
1418 | return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url)) | |
1303 |
|
1419 | |||
1304 |
def notify_reviewers(self, pull_request, |
|
1420 | def _notify_reviewers(self, pull_request, user_ids, role, user): | |
1305 | # notification to reviewers |
|
1421 | # notification to reviewers/observers | |
1306 |
if not |
|
1422 | if not user_ids: | |
1307 | return |
|
1423 | return | |
1308 |
|
1424 | |||
1309 |
log.debug('Notify following |
|
1425 | log.debug('Notify following %s users about pull-request %s', role, user_ids) | |
1310 |
|
1426 | |||
1311 | pull_request_obj = pull_request |
|
1427 | pull_request_obj = pull_request | |
1312 | # get the current participants of this pull request |
|
1428 | # get the current participants of this pull request | |
1313 |
recipients = |
|
1429 | recipients = user_ids | |
1314 | notification_type = EmailNotificationModel.TYPE_PULL_REQUEST |
|
1430 | notification_type = EmailNotificationModel.TYPE_PULL_REQUEST | |
1315 |
|
1431 | |||
1316 | pr_source_repo = pull_request_obj.source_repo |
|
1432 | pr_source_repo = pull_request_obj.source_repo | |
@@ -1332,8 +1448,10 b' class PullRequestModel(BaseModel):' | |||||
1332 | (x.raw_id, x.message) |
|
1448 | (x.raw_id, x.message) | |
1333 | for x in map(pr_source_repo.get_commit, pull_request.revisions)] |
|
1449 | for x in map(pr_source_repo.get_commit, pull_request.revisions)] | |
1334 |
|
1450 | |||
|
1451 | current_rhodecode_user = user | |||
1335 | kwargs = { |
|
1452 | kwargs = { | |
1336 |
'user': |
|
1453 | 'user': current_rhodecode_user, | |
|
1454 | 'pull_request_author': pull_request.author, | |||
1337 | 'pull_request': pull_request_obj, |
|
1455 | 'pull_request': pull_request_obj, | |
1338 | 'pull_request_commits': pull_request_commits, |
|
1456 | 'pull_request_commits': pull_request_commits, | |
1339 |
|
1457 | |||
@@ -1345,6 +1463,7 b' class PullRequestModel(BaseModel):' | |||||
1345 |
|
1463 | |||
1346 | 'pull_request_url': pr_url, |
|
1464 | 'pull_request_url': pr_url, | |
1347 | 'thread_ids': [pr_url], |
|
1465 | 'thread_ids': [pr_url], | |
|
1466 | 'user_role': role | |||
1348 | } |
|
1467 | } | |
1349 |
|
1468 | |||
1350 | # pre-generate the subject for notification itself |
|
1469 | # pre-generate the subject for notification itself | |
@@ -1353,7 +1472,7 b' class PullRequestModel(BaseModel):' | |||||
1353 |
|
1472 | |||
1354 | # create notification objects, and emails |
|
1473 | # create notification objects, and emails | |
1355 | NotificationModel().create( |
|
1474 | NotificationModel().create( | |
1356 |
created_by= |
|
1475 | created_by=current_rhodecode_user, | |
1357 | notification_subject=subject, |
|
1476 | notification_subject=subject, | |
1358 | notification_body=body_plaintext, |
|
1477 | notification_body=body_plaintext, | |
1359 | notification_type=notification_type, |
|
1478 | notification_type=notification_type, | |
@@ -1361,6 +1480,14 b' class PullRequestModel(BaseModel):' | |||||
1361 | email_kwargs=kwargs, |
|
1480 | email_kwargs=kwargs, | |
1362 | ) |
|
1481 | ) | |
1363 |
|
1482 | |||
|
1483 | def notify_reviewers(self, pull_request, reviewers_ids, user): | |||
|
1484 | return self._notify_reviewers(pull_request, reviewers_ids, | |||
|
1485 | PullRequestReviewers.ROLE_REVIEWER, user) | |||
|
1486 | ||||
|
1487 | def notify_observers(self, pull_request, observers_ids, user): | |||
|
1488 | return self._notify_reviewers(pull_request, observers_ids, | |||
|
1489 | PullRequestReviewers.ROLE_OBSERVER, user) | |||
|
1490 | ||||
1364 | def notify_users(self, pull_request, updating_user, ancestor_commit_id, |
|
1491 | def notify_users(self, pull_request, updating_user, ancestor_commit_id, | |
1365 | commit_changes, file_changes): |
|
1492 | commit_changes, file_changes): | |
1366 |
|
1493 | |||
@@ -1874,11 +2001,13 b' class PullRequestModel(BaseModel):' | |||||
1874 | try: |
|
2001 | try: | |
1875 | from rc_reviewers.utils import get_default_reviewers_data |
|
2002 | from rc_reviewers.utils import get_default_reviewers_data | |
1876 | from rc_reviewers.utils import validate_default_reviewers |
|
2003 | from rc_reviewers.utils import validate_default_reviewers | |
|
2004 | from rc_reviewers.utils import validate_observers | |||
1877 | except ImportError: |
|
2005 | except ImportError: | |
1878 | from rhodecode.apps.repository.utils import get_default_reviewers_data |
|
2006 | from rhodecode.apps.repository.utils import get_default_reviewers_data | |
1879 | from rhodecode.apps.repository.utils import validate_default_reviewers |
|
2007 | from rhodecode.apps.repository.utils import validate_default_reviewers | |
|
2008 | from rhodecode.apps.repository.utils import validate_observers | |||
1880 |
|
2009 | |||
1881 | return get_default_reviewers_data, validate_default_reviewers |
|
2010 | return get_default_reviewers_data, validate_default_reviewers, validate_observers | |
1882 |
|
2011 | |||
1883 |
|
2012 | |||
1884 | class MergeCheck(object): |
|
2013 | class MergeCheck(object): |
@@ -1700,8 +1700,33 b' table.group_members {' | |||||
1700 | } |
|
1700 | } | |
1701 |
|
1701 | |||
1702 | .reviewer_ac .ac-input { |
|
1702 | .reviewer_ac .ac-input { | |
|
1703 | width: 98%; | |||
|
1704 | margin-bottom: 1em; | |||
|
1705 | } | |||
|
1706 | ||||
|
1707 | .observer_ac .ac-input { | |||
|
1708 | width: 98%; | |||
|
1709 | margin-bottom: 1em; | |||
|
1710 | } | |||
|
1711 | ||||
|
1712 | .rule-table { | |||
1703 | width: 100%; |
|
1713 | width: 100%; | |
1704 | margin-bottom: 1em; |
|
1714 | } | |
|
1715 | ||||
|
1716 | .rule-table td { | |||
|
1717 | ||||
|
1718 | } | |||
|
1719 | ||||
|
1720 | .rule-table .td-role { | |||
|
1721 | width: 100px | |||
|
1722 | } | |||
|
1723 | ||||
|
1724 | .rule-table .td-mandatory { | |||
|
1725 | width: 100px | |||
|
1726 | } | |||
|
1727 | ||||
|
1728 | .rule-table .td-group-votes { | |||
|
1729 | width: 150px | |||
1705 | } |
|
1730 | } | |
1706 |
|
1731 | |||
1707 | .compare_view_commits tr{ |
|
1732 | .compare_view_commits tr{ |
@@ -94,21 +94,26 b' var getTitleAndDescription = function(so' | |||||
94 | }; |
|
94 | }; | |
95 |
|
95 | |||
96 |
|
96 | |||
97 | ReviewersController = function () { |
|
97 | window.ReviewersController = function () { | |
98 | var self = this; |
|
98 | var self = this; | |
|
99 | this.$loadingIndicator = $('.calculate-reviewers'); | |||
99 | this.$reviewRulesContainer = $('#review_rules'); |
|
100 | this.$reviewRulesContainer = $('#review_rules'); | |
100 | this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules'); |
|
101 | this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules'); | |
101 | this.$userRule = $('.pr-user-rule-container'); |
|
102 | this.$userRule = $('.pr-user-rule-container'); | |
102 | this.forbidReviewUsers = undefined; |
|
|||
103 | this.$reviewMembers = $('#review_members'); |
|
103 | this.$reviewMembers = $('#review_members'); | |
|
104 | this.$observerMembers = $('#observer_members'); | |||
|
105 | ||||
104 | this.currentRequest = null; |
|
106 | this.currentRequest = null; | |
105 | this.diffData = null; |
|
107 | this.diffData = null; | |
106 | this.enabledRules = []; |
|
108 | this.enabledRules = []; | |
|
109 | // sync with db.py entries | |||
|
110 | this.ROLE_REVIEWER = 'reviewer'; | |||
|
111 | this.ROLE_OBSERVER = 'observer' | |||
107 |
|
112 | |||
108 | //dummy handler, we might register our own later |
|
113 | //dummy handler, we might register our own later | |
109 | this.diffDataHandler = function(data){}; |
|
114 | this.diffDataHandler = function (data) {}; | |
110 |
|
115 | |||
111 |
this.defaultForbid |
|
116 | this.defaultForbidUsers = function () { | |
112 | return [ |
|
117 | return [ | |
113 | { |
|
118 | { | |
114 | 'username': 'default', |
|
119 | 'username': 'default', | |
@@ -117,6 +122,9 b' ReviewersController = function () {' | |||||
117 | ]; |
|
122 | ]; | |
118 | }; |
|
123 | }; | |
119 |
|
124 | |||
|
125 | // init default forbidden users | |||
|
126 | this.forbidUsers = this.defaultForbidUsers(); | |||
|
127 | ||||
120 | this.hideReviewRules = function () { |
|
128 | this.hideReviewRules = function () { | |
121 | self.$reviewRulesContainer.hide(); |
|
129 | self.$reviewRulesContainer.hide(); | |
122 | $(self.$userRule.selector).hide(); |
|
130 | $(self.$userRule.selector).hide(); | |
@@ -133,11 +141,40 b' ReviewersController = function () {' | |||||
133 | return '<div>- {0}</div>'.format(ruleText) |
|
141 | return '<div>- {0}</div>'.format(ruleText) | |
134 | }; |
|
142 | }; | |
135 |
|
143 | |||
|
144 | this.increaseCounter = function(role) { | |||
|
145 | if (role === self.ROLE_REVIEWER) { | |||
|
146 | var $elem = $('#reviewers-cnt') | |||
|
147 | var cnt = parseInt($elem.data('count') || 0) | |||
|
148 | cnt +=1 | |||
|
149 | $elem.html(cnt); | |||
|
150 | $elem.data('count', cnt); | |||
|
151 | } | |||
|
152 | else if (role === self.ROLE_OBSERVER) { | |||
|
153 | var $elem = $('#observers-cnt'); | |||
|
154 | var cnt = parseInt($elem.data('count') || 0) | |||
|
155 | cnt +=1 | |||
|
156 | $elem.html(cnt); | |||
|
157 | $elem.data('count', cnt); | |||
|
158 | } | |||
|
159 | } | |||
|
160 | ||||
|
161 | this.resetCounter = function () { | |||
|
162 | var $elem = $('#reviewers-cnt'); | |||
|
163 | ||||
|
164 | $elem.data('count', 0); | |||
|
165 | $elem.html(0); | |||
|
166 | ||||
|
167 | var $elem = $('#observers-cnt'); | |||
|
168 | ||||
|
169 | $elem.data('count', 0); | |||
|
170 | $elem.html(0); | |||
|
171 | } | |||
|
172 | ||||
136 | this.loadReviewRules = function (data) { |
|
173 | this.loadReviewRules = function (data) { | |
137 | self.diffData = data; |
|
174 | self.diffData = data; | |
138 |
|
175 | |||
139 | // reset forbidden Users |
|
176 | // reset forbidden Users | |
140 |
this.forbid |
|
177 | this.forbidUsers = self.defaultForbidUsers(); | |
141 |
|
178 | |||
142 | // reset state of review rules |
|
179 | // reset state of review rules | |
143 | self.$rulesList.html(''); |
|
180 | self.$rulesList.html(''); | |
@@ -148,7 +185,7 b' ReviewersController = function () {' | |||||
148 | self.addRule( |
|
185 | self.addRule( | |
149 | _gettext('All reviewers must vote.')) |
|
186 | _gettext('All reviewers must vote.')) | |
150 | ); |
|
187 | ); | |
151 |
return self.forbid |
|
188 | return self.forbidUsers | |
152 | } |
|
189 | } | |
153 |
|
190 | |||
154 | if (data.rules.voting !== undefined) { |
|
191 | if (data.rules.voting !== undefined) { | |
@@ -195,7 +232,7 b' ReviewersController = function () {' | |||||
195 | } |
|
232 | } | |
196 |
|
233 | |||
197 | if (data.rules.forbid_author_to_review) { |
|
234 | if (data.rules.forbid_author_to_review) { | |
198 |
self.forbid |
|
235 | self.forbidUsers.push(data.rules_data.pr_author); | |
199 | self.$rulesList.append( |
|
236 | self.$rulesList.append( | |
200 | self.addRule( |
|
237 | self.addRule( | |
201 | _gettext('Author is not allowed to be a reviewer.')) |
|
238 | _gettext('Author is not allowed to be a reviewer.')) | |
@@ -206,9 +243,8 b' ReviewersController = function () {' | |||||
206 |
|
243 | |||
207 | if (data.rules_data.forbidden_users) { |
|
244 | if (data.rules_data.forbidden_users) { | |
208 | $.each(data.rules_data.forbidden_users, function (index, member_data) { |
|
245 | $.each(data.rules_data.forbidden_users, function (index, member_data) { | |
209 |
self.forbid |
|
246 | self.forbidUsers.push(member_data) | |
210 | }); |
|
247 | }); | |
211 |
|
||||
212 | } |
|
248 | } | |
213 |
|
249 | |||
214 | self.$rulesList.append( |
|
250 | self.$rulesList.append( | |
@@ -223,9 +259,31 b' ReviewersController = function () {' | |||||
223 | _gettext('No review rules set.')) |
|
259 | _gettext('No review rules set.')) | |
224 | } |
|
260 | } | |
225 |
|
261 | |||
226 |
return self.forbid |
|
262 | return self.forbidUsers | |
227 | }; |
|
263 | }; | |
228 |
|
264 | |||
|
265 | this.emptyTables = function () { | |||
|
266 | self.emptyReviewersTable(); | |||
|
267 | self.emptyObserversTable(); | |||
|
268 | ||||
|
269 | // Also reset counters. | |||
|
270 | self.resetCounter(); | |||
|
271 | } | |||
|
272 | ||||
|
273 | this.emptyReviewersTable = function (withText) { | |||
|
274 | self.$reviewMembers.empty(); | |||
|
275 | if (withText !== undefined) { | |||
|
276 | self.$reviewMembers.html(withText) | |||
|
277 | } | |||
|
278 | }; | |||
|
279 | ||||
|
280 | this.emptyObserversTable = function (withText) { | |||
|
281 | self.$observerMembers.empty(); | |||
|
282 | if (withText !== undefined) { | |||
|
283 | self.$observerMembers.html(withText) | |||
|
284 | } | |||
|
285 | } | |||
|
286 | ||||
229 | this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) { |
|
287 | this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) { | |
230 |
|
288 | |||
231 | if (self.currentRequest) { |
|
289 | if (self.currentRequest) { | |
@@ -233,19 +291,21 b' ReviewersController = function () {' | |||||
233 | self.currentRequest.abort(); |
|
291 | self.currentRequest.abort(); | |
234 | } |
|
292 | } | |
235 |
|
293 | |||
236 | $('.calculate-reviewers').show(); |
|
294 | self.$loadingIndicator.show(); | |
237 | // reset reviewer members |
|
295 | ||
238 | self.$reviewMembers.empty(); |
|
296 | // reset reviewer/observe members | |
|
297 | self.emptyTables(); | |||
239 |
|
298 | |||
240 | prButtonLock(true, null, 'reviewers'); |
|
299 | prButtonLock(true, null, 'reviewers'); | |
241 | $('#user').hide(); // hide user autocomplete before load |
|
300 | $('#user').hide(); // hide user autocomplete before load | |
|
301 | $('#observer').hide(); //hide observer autocomplete before load | |||
242 |
|
302 | |||
243 | // lock PR button, so we cannot send PR before it's calculated |
|
303 | // lock PR button, so we cannot send PR before it's calculated | |
244 | prButtonLock(true, _gettext('Loading diff ...'), 'compare'); |
|
304 | prButtonLock(true, _gettext('Loading diff ...'), 'compare'); | |
245 |
|
305 | |||
246 | if (sourceRef.length !== 3 || targetRef.length !== 3) { |
|
306 | if (sourceRef.length !== 3 || targetRef.length !== 3) { | |
247 | // don't load defaults in case we're missing some refs... |
|
307 | // don't load defaults in case we're missing some refs... | |
248 | $('.calculate-reviewers').hide(); |
|
308 | self.$loadingIndicator.hide(); | |
249 | return |
|
309 | return | |
250 | } |
|
310 | } | |
251 |
|
311 | |||
@@ -272,11 +332,16 b' ReviewersController = function () {' | |||||
272 |
|
332 | |||
273 | for (var i = 0; i < data.reviewers.length; i++) { |
|
333 | for (var i = 0; i < data.reviewers.length; i++) { | |
274 | var reviewer = data.reviewers[i]; |
|
334 | var reviewer = data.reviewers[i]; | |
275 | self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory); |
|
335 | // load reviewer rules from the repo data | |
|
336 | self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role); | |||
276 | } |
|
337 | } | |
277 | $('.calculate-reviewers').hide(); |
|
338 | ||
|
339 | ||||
|
340 | self.$loadingIndicator.hide(); | |||
278 | prButtonLock(false, null, 'reviewers'); |
|
341 | prButtonLock(false, null, 'reviewers'); | |
279 | $('#user').show(); // show user autocomplete after load |
|
342 | ||
|
343 | $('#user').show(); // show user autocomplete before load | |||
|
344 | $('#observer').show(); // show observer autocomplete before load | |||
280 |
|
345 | |||
281 | var commitElements = data["diff_info"]['commits']; |
|
346 | var commitElements = data["diff_info"]['commits']; | |
282 |
|
347 | |||
@@ -292,7 +357,7 b' ReviewersController = function () {' | |||||
292 |
|
357 | |||
293 | }, |
|
358 | }, | |
294 | error: function (jqXHR, textStatus, errorThrown) { |
|
359 | error: function (jqXHR, textStatus, errorThrown) { | |
295 | var prefix = "Loading diff and reviewers failed\n" |
|
360 | var prefix = "Loading diff and reviewers/observers failed\n" | |
296 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
|
361 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); | |
297 | ajaxErrorSwal(message); |
|
362 | ajaxErrorSwal(message); | |
298 | } |
|
363 | } | |
@@ -301,7 +366,7 b' ReviewersController = function () {' | |||||
301 | }; |
|
366 | }; | |
302 |
|
367 | |||
303 | // check those, refactor |
|
368 | // check those, refactor | |
304 |
this.remove |
|
369 | this.removeMember = function (reviewer_id, mark_delete) { | |
305 | var reviewer = $('#reviewer_{0}'.format(reviewer_id)); |
|
370 | var reviewer = $('#reviewer_{0}'.format(reviewer_id)); | |
306 |
|
371 | |||
307 | if (typeof (mark_delete) === undefined) { |
|
372 | if (typeof (mark_delete) === undefined) { | |
@@ -312,6 +377,7 b' ReviewersController = function () {' | |||||
312 | if (reviewer) { |
|
377 | if (reviewer) { | |
313 | // now delete the input |
|
378 | // now delete the input | |
314 | $('#reviewer_{0} input'.format(reviewer_id)).remove(); |
|
379 | $('#reviewer_{0} input'.format(reviewer_id)).remove(); | |
|
380 | $('#reviewer_{0}_rules input'.format(reviewer_id)).remove(); | |||
315 | // mark as to-delete |
|
381 | // mark as to-delete | |
316 | var obj = $('#reviewer_{0}_name'.format(reviewer_id)); |
|
382 | var obj = $('#reviewer_{0}_name'.format(reviewer_id)); | |
317 | obj.addClass('to-delete'); |
|
383 | obj.addClass('to-delete'); | |
@@ -322,27 +388,26 b' ReviewersController = function () {' | |||||
322 | } |
|
388 | } | |
323 | }; |
|
389 | }; | |
324 |
|
390 | |||
325 | this.reviewMemberEntry = function () { |
|
391 | this.addMember = function (reviewer_obj, reasons, mandatory, role) { | |
326 |
|
392 | |||
327 | }; |
|
|||
328 |
|
||||
329 | this.addReviewMember = function (reviewer_obj, reasons, mandatory) { |
|
|||
330 | var id = reviewer_obj.user_id; |
|
393 | var id = reviewer_obj.user_id; | |
331 | var username = reviewer_obj.username; |
|
394 | var username = reviewer_obj.username; | |
332 |
|
395 | |||
333 |
|
|
396 | reasons = reasons || []; | |
334 |
|
|
397 | mandatory = mandatory || false; | |
|
398 | role = role || self.ROLE_REVIEWER | |||
335 |
|
399 | |||
336 | // register IDS to check if we don't have this ID already in |
|
400 | // register current set IDS to check if we don't have this ID already in | |
|
401 | // and prevent duplicates | |||
337 | var currentIds = []; |
|
402 | var currentIds = []; | |
338 |
|
403 | |||
339 |
$.each( |
|
404 | $.each($('.reviewer_entry'), function (index, value) { | |
340 | currentIds.push($(value).data('reviewerUserId')) |
|
405 | currentIds.push($(value).data('reviewerUserId')) | |
341 | }) |
|
406 | }) | |
342 |
|
407 | |||
343 | var userAllowedReview = function (userId) { |
|
408 | var userAllowedReview = function (userId) { | |
344 | var allowed = true; |
|
409 | var allowed = true; | |
345 |
$.each(self.forbid |
|
410 | $.each(self.forbidUsers, function (index, member_data) { | |
346 | if (parseInt(userId) === member_data['user_id']) { |
|
411 | if (parseInt(userId) === member_data['user_id']) { | |
347 | allowed = false; |
|
412 | allowed = false; | |
348 | return false // breaks the loop |
|
413 | return false // breaks the loop | |
@@ -352,6 +417,7 b' ReviewersController = function () {' | |||||
352 | }; |
|
417 | }; | |
353 |
|
418 | |||
354 | var userAllowed = userAllowedReview(id); |
|
419 | var userAllowed = userAllowedReview(id); | |
|
420 | ||||
355 | if (!userAllowed) { |
|
421 | if (!userAllowed) { | |
356 | alert(_gettext('User `{0}` not allowed to be a reviewer').format(username)); |
|
422 | alert(_gettext('User `{0}` not allowed to be a reviewer').format(username)); | |
357 | } else { |
|
423 | } else { | |
@@ -359,11 +425,13 b' ReviewersController = function () {' | |||||
359 | var alreadyReviewer = currentIds.indexOf(id) != -1; |
|
425 | var alreadyReviewer = currentIds.indexOf(id) != -1; | |
360 |
|
426 | |||
361 | if (alreadyReviewer) { |
|
427 | if (alreadyReviewer) { | |
362 | alert(_gettext('User `{0}` already in reviewers').format(username)); |
|
428 | alert(_gettext('User `{0}` already in reviewers/observers').format(username)); | |
363 | } else { |
|
429 | } else { | |
|
430 | ||||
364 | var reviewerEntry = renderTemplate('reviewMemberEntry', { |
|
431 | var reviewerEntry = renderTemplate('reviewMemberEntry', { | |
365 | 'member': reviewer_obj, |
|
432 | 'member': reviewer_obj, | |
366 | 'mandatory': mandatory, |
|
433 | 'mandatory': mandatory, | |
|
434 | 'role': role, | |||
367 | 'reasons': reasons, |
|
435 | 'reasons': reasons, | |
368 | 'allowed_to_update': true, |
|
436 | 'allowed_to_update': true, | |
369 | 'review_status': 'not_reviewed', |
|
437 | 'review_status': 'not_reviewed', | |
@@ -372,16 +440,32 b' ReviewersController = function () {' | |||||
372 | 'create': true, |
|
440 | 'create': true, | |
373 | 'rule_show': true, |
|
441 | 'rule_show': true, | |
374 | }) |
|
442 | }) | |
|
443 | ||||
|
444 | if (role === self.ROLE_REVIEWER) { | |||
375 | $(self.$reviewMembers.selector).append(reviewerEntry); |
|
445 | $(self.$reviewMembers.selector).append(reviewerEntry); | |
|
446 | self.increaseCounter(self.ROLE_REVIEWER); | |||
|
447 | $('#reviewer-empty-msg').remove() | |||
|
448 | } | |||
|
449 | else if (role === self.ROLE_OBSERVER) { | |||
|
450 | $(self.$observerMembers.selector).append(reviewerEntry); | |||
|
451 | self.increaseCounter(self.ROLE_OBSERVER); | |||
|
452 | $('#observer-empty-msg').remove(); | |||
|
453 | } | |||
|
454 | ||||
376 | tooltipActivate(); |
|
455 | tooltipActivate(); | |
377 | } |
|
456 | } | |
378 | } |
|
457 | } | |
379 |
|
458 | |||
380 | }; |
|
459 | }; | |
381 |
|
460 | |||
382 | this.updateReviewers = function (repo_name, pull_request_id) { |
|
461 | this.updateReviewers = function (repo_name, pull_request_id, role) { | |
|
462 | if (role === 'reviewer') { | |||
383 | var postData = $('#reviewers input').serialize(); |
|
463 | var postData = $('#reviewers input').serialize(); | |
384 | _updatePullRequest(repo_name, pull_request_id, postData); |
|
464 | _updatePullRequest(repo_name, pull_request_id, postData); | |
|
465 | } else if (role === 'observer') { | |||
|
466 | var postData = $('#observers input').serialize(); | |||
|
467 | _updatePullRequest(repo_name, pull_request_id, postData); | |||
|
468 | } | |||
385 | }; |
|
469 | }; | |
386 |
|
470 | |||
387 | this.handleDiffData = function (data) { |
|
471 | this.handleDiffData = function (data) { | |
@@ -449,23 +533,14 b' var editPullRequest = function(repo_name' | |||||
449 |
|
533 | |||
450 |
|
534 | |||
451 | /** |
|
535 | /** | |
452 | * Reviewer autocomplete |
|
536 | * autocomplete handler for reviewers/observers | |
453 | */ |
|
537 | */ | |
454 |
var |
|
538 | var autoCompleteHandler = function (inputId, controller, role) { | |
455 | $(inputId).autocomplete({ |
|
539 | ||
456 | serviceUrl: pyroutes.url('user_autocomplete_data'), |
|
540 | return function (element, data) { | |
457 | minChars:2, |
|
|||
458 | maxHeight:400, |
|
|||
459 | deferRequestBy: 300, //miliseconds |
|
|||
460 | showNoSuggestionNotice: true, |
|
|||
461 | tabDisabled: true, |
|
|||
462 | autoSelectFirst: true, |
|
|||
463 | params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true }, |
|
|||
464 | formatResult: autocompleteFormatResult, |
|
|||
465 | lookupFilter: autocompleteFilterResult, |
|
|||
466 | onSelect: function(element, data) { |
|
|||
467 | var mandatory = false; |
|
541 | var mandatory = false; | |
468 |
var reasons = [_gettext('added manually by "{0}"').format( |
|
542 | var reasons = [_gettext('added manually by "{0}"').format( | |
|
543 | templateContext.rhodecode_user.username)]; | |||
469 |
|
544 | |||
470 | // add whole user groups |
|
545 | // add whole user groups | |
471 | if (data.value_type == 'user_group') { |
|
546 | if (data.value_type == 'user_group') { | |
@@ -477,7 +552,7 b' var ReviewerAutoComplete = function(inpu' | |||||
477 | reviewer['gravatar_link'] = member_data['icon_link']; |
|
552 | reviewer['gravatar_link'] = member_data['icon_link']; | |
478 | reviewer['user_link'] = member_data['profile_link']; |
|
553 | reviewer['user_link'] = member_data['profile_link']; | |
479 | reviewer['rules'] = []; |
|
554 | reviewer['rules'] = []; | |
480 |
|
|
555 | controller.addMember(reviewer, reasons, mandatory, role); | |
481 | }) |
|
556 | }) | |
482 | } |
|
557 | } | |
483 | // add single user |
|
558 | // add single user | |
@@ -487,14 +562,71 b' var ReviewerAutoComplete = function(inpu' | |||||
487 | reviewer['gravatar_link'] = data['icon_link']; |
|
562 | reviewer['gravatar_link'] = data['icon_link']; | |
488 | reviewer['user_link'] = data['profile_link']; |
|
563 | reviewer['user_link'] = data['profile_link']; | |
489 | reviewer['rules'] = []; |
|
564 | reviewer['rules'] = []; | |
490 |
|
|
565 | controller.addMember(reviewer, reasons, mandatory, role); | |
491 | } |
|
566 | } | |
492 |
|
567 | |||
493 | $(inputId).val(''); |
|
568 | $(inputId).val(''); | |
494 | } |
|
569 | } | |
|
570 | } | |||
|
571 | ||||
|
572 | /** | |||
|
573 | * Reviewer autocomplete | |||
|
574 | */ | |||
|
575 | var ReviewerAutoComplete = function (inputId, controller) { | |||
|
576 | var self = this; | |||
|
577 | self.controller = controller; | |||
|
578 | self.inputId = inputId; | |||
|
579 | var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER); | |||
|
580 | ||||
|
581 | $(inputId).autocomplete({ | |||
|
582 | serviceUrl: pyroutes.url('user_autocomplete_data'), | |||
|
583 | minChars: 2, | |||
|
584 | maxHeight: 400, | |||
|
585 | deferRequestBy: 300, //miliseconds | |||
|
586 | showNoSuggestionNotice: true, | |||
|
587 | tabDisabled: true, | |||
|
588 | autoSelectFirst: true, | |||
|
589 | params: { | |||
|
590 | user_id: templateContext.rhodecode_user.user_id, | |||
|
591 | user_groups: true, | |||
|
592 | user_groups_expand: true, | |||
|
593 | skip_default_user: true | |||
|
594 | }, | |||
|
595 | formatResult: autocompleteFormatResult, | |||
|
596 | lookupFilter: autocompleteFilterResult, | |||
|
597 | onSelect: handler | |||
495 | }); |
|
598 | }); | |
496 | }; |
|
599 | }; | |
497 |
|
600 | |||
|
601 | /** | |||
|
602 | * Observers autocomplete | |||
|
603 | */ | |||
|
604 | var ObserverAutoComplete = function(inputId, controller) { | |||
|
605 | var self = this; | |||
|
606 | self.controller = controller; | |||
|
607 | self.inputId = inputId; | |||
|
608 | var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER); | |||
|
609 | ||||
|
610 | $(inputId).autocomplete({ | |||
|
611 | serviceUrl: pyroutes.url('user_autocomplete_data'), | |||
|
612 | minChars: 2, | |||
|
613 | maxHeight: 400, | |||
|
614 | deferRequestBy: 300, //miliseconds | |||
|
615 | showNoSuggestionNotice: true, | |||
|
616 | tabDisabled: true, | |||
|
617 | autoSelectFirst: true, | |||
|
618 | params: { | |||
|
619 | user_id: templateContext.rhodecode_user.user_id, | |||
|
620 | user_groups: true, | |||
|
621 | user_groups_expand: true, | |||
|
622 | skip_default_user: true | |||
|
623 | }, | |||
|
624 | formatResult: autocompleteFormatResult, | |||
|
625 | lookupFilter: autocompleteFilterResult, | |||
|
626 | onSelect: handler | |||
|
627 | }); | |||
|
628 | } | |||
|
629 | ||||
498 |
|
630 | |||
499 | window.VersionController = function () { |
|
631 | window.VersionController = function () { | |
500 | var self = this; |
|
632 | var self = this; | |
@@ -504,7 +636,7 b' window.VersionController = function () {' | |||||
504 |
|
636 | |||
505 | this.adjustRadioSelectors = function (curNode) { |
|
637 | this.adjustRadioSelectors = function (curNode) { | |
506 | var getVal = function (item) { |
|
638 | var getVal = function (item) { | |
507 | if (item == 'latest') { |
|
639 | if (item === 'latest') { | |
508 | return Number.MAX_SAFE_INTEGER |
|
640 | return Number.MAX_SAFE_INTEGER | |
509 | } |
|
641 | } | |
510 | else { |
|
642 | else { | |
@@ -663,6 +795,7 b' window.UpdatePrController = function () ' | |||||
663 | }; |
|
795 | }; | |
664 | }; |
|
796 | }; | |
665 |
|
797 | |||
|
798 | ||||
666 | /** |
|
799 | /** | |
667 | * Reviewer display panel |
|
800 | * Reviewer display panel | |
668 | */ |
|
801 | */ | |
@@ -702,14 +835,24 b' window.ReviewersPanel = {' | |||||
702 | }, |
|
835 | }, | |
703 |
|
836 | |||
704 | renderReviewers: function () { |
|
837 | renderReviewers: function () { | |
|
838 | if (this.setReviewers.reviewers === undefined) { | |||
|
839 | return | |||
|
840 | } | |||
|
841 | if (this.setReviewers.reviewers.length === 0) { | |||
|
842 | reviewersController.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>'); | |||
|
843 | return | |||
|
844 | } | |||
705 |
|
845 | |||
706 | $('#review_members').html('') |
|
846 | reviewersController.emptyReviewersTable(); | |
|
847 | ||||
707 | $.each(this.setReviewers.reviewers, function (key, val) { |
|
848 | $.each(this.setReviewers.reviewers, function (key, val) { | |
|
849 | ||||
708 | var member = val; |
|
850 | var member = val; | |
709 |
|
851 | if (member.role === reviewersController.ROLE_REVIEWER) { | ||
710 | var entry = renderTemplate('reviewMemberEntry', { |
|
852 | var entry = renderTemplate('reviewMemberEntry', { | |
711 | 'member': member, |
|
853 | 'member': member, | |
712 | 'mandatory': member.mandatory, |
|
854 | 'mandatory': member.mandatory, | |
|
855 | 'role': member.role, | |||
713 | 'reasons': member.reasons, |
|
856 | 'reasons': member.reasons, | |
714 | 'allowed_to_update': member.allowed_to_update, |
|
857 | 'allowed_to_update': member.allowed_to_update, | |
715 | 'review_status': member.review_status, |
|
858 | 'review_status': member.review_status, | |
@@ -718,10 +861,11 b' window.ReviewersPanel = {' | |||||
718 | 'create': false |
|
861 | 'create': false | |
719 | }); |
|
862 | }); | |
720 |
|
863 | |||
721 |
$( |
|
864 | $(reviewersController.$reviewMembers.selector).append(entry) | |
|
865 | } | |||
722 | }); |
|
866 | }); | |
|
867 | ||||
723 | tooltipActivate(); |
|
868 | tooltipActivate(); | |
724 |
|
||||
725 | }, |
|
869 | }, | |
726 |
|
870 | |||
727 | edit: function (event) { |
|
871 | edit: function (event) { | |
@@ -739,10 +883,142 b' window.ReviewersPanel = {' | |||||
739 | this.addButton.hide(); |
|
883 | this.addButton.hide(); | |
740 | $(this.removeButtons.selector).css('visibility', 'hidden'); |
|
884 | $(this.removeButtons.selector).css('visibility', 'hidden'); | |
741 | // hide review rules |
|
885 | // hide review rules | |
742 | reviewersController.hideReviewRules() |
|
886 | reviewersController.hideReviewRules(); | |
743 | } |
|
887 | } | |
744 | }; |
|
888 | }; | |
745 |
|
889 | |||
|
890 | /** | |||
|
891 | * Reviewer display panel | |||
|
892 | */ | |||
|
893 | window.ObserversPanel = { | |||
|
894 | editButton: null, | |||
|
895 | closeButton: null, | |||
|
896 | addButton: null, | |||
|
897 | removeButtons: null, | |||
|
898 | reviewRules: null, | |||
|
899 | setReviewers: null, | |||
|
900 | ||||
|
901 | setSelectors: function () { | |||
|
902 | var self = this; | |||
|
903 | self.editButton = $('#open_edit_observers'); | |||
|
904 | self.closeButton =$('#close_edit_observers'); | |||
|
905 | self.addButton = $('#add_observer'); | |||
|
906 | self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove'); | |||
|
907 | }, | |||
|
908 | ||||
|
909 | init: function (reviewRules, setReviewers) { | |||
|
910 | var self = this; | |||
|
911 | self.setSelectors(); | |||
|
912 | ||||
|
913 | this.reviewRules = reviewRules; | |||
|
914 | this.setReviewers = setReviewers; | |||
|
915 | ||||
|
916 | this.editButton.on('click', function (e) { | |||
|
917 | self.edit(); | |||
|
918 | }); | |||
|
919 | this.closeButton.on('click', function (e) { | |||
|
920 | self.close(); | |||
|
921 | self.renderObservers(); | |||
|
922 | }); | |||
|
923 | ||||
|
924 | self.renderObservers(); | |||
|
925 | ||||
|
926 | }, | |||
|
927 | ||||
|
928 | renderObservers: function () { | |||
|
929 | if (this.setReviewers.observers === undefined) { | |||
|
930 | return | |||
|
931 | } | |||
|
932 | if (this.setReviewers.observers.length === 0) { | |||
|
933 | reviewersController.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>'); | |||
|
934 | return | |||
|
935 | } | |||
|
936 | ||||
|
937 | reviewersController.emptyObserversTable(); | |||
|
938 | ||||
|
939 | $.each(this.setReviewers.observers, function (key, val) { | |||
|
940 | var member = val; | |||
|
941 | if (member.role === reviewersController.ROLE_OBSERVER) { | |||
|
942 | var entry = renderTemplate('reviewMemberEntry', { | |||
|
943 | 'member': member, | |||
|
944 | 'mandatory': member.mandatory, | |||
|
945 | 'role': member.role, | |||
|
946 | 'reasons': member.reasons, | |||
|
947 | 'allowed_to_update': member.allowed_to_update, | |||
|
948 | 'review_status': member.review_status, | |||
|
949 | 'review_status_label': member.review_status_label, | |||
|
950 | 'user_group': member.user_group, | |||
|
951 | 'create': false | |||
|
952 | }); | |||
|
953 | ||||
|
954 | $(reviewersController.$observerMembers.selector).append(entry) | |||
|
955 | } | |||
|
956 | }); | |||
|
957 | ||||
|
958 | tooltipActivate(); | |||
|
959 | }, | |||
|
960 | ||||
|
961 | edit: function (event) { | |||
|
962 | this.editButton.hide(); | |||
|
963 | this.closeButton.show(); | |||
|
964 | this.addButton.show(); | |||
|
965 | $(this.removeButtons.selector).css('visibility', 'visible'); | |||
|
966 | }, | |||
|
967 | ||||
|
968 | close: function (event) { | |||
|
969 | this.editButton.show(); | |||
|
970 | this.closeButton.hide(); | |||
|
971 | this.addButton.hide(); | |||
|
972 | $(this.removeButtons.selector).css('visibility', 'hidden'); | |||
|
973 | } | |||
|
974 | ||||
|
975 | }; | |||
|
976 | ||||
|
977 | window.PRDetails = { | |||
|
978 | editButton: null, | |||
|
979 | closeButton: null, | |||
|
980 | deleteButton: null, | |||
|
981 | viewFields: null, | |||
|
982 | editFields: null, | |||
|
983 | ||||
|
984 | setSelectors: function () { | |||
|
985 | var self = this; | |||
|
986 | self.editButton = $('#open_edit_pullrequest') | |||
|
987 | self.closeButton = $('#close_edit_pullrequest') | |||
|
988 | self.deleteButton = $('#delete_pullrequest') | |||
|
989 | self.viewFields = $('#pr-desc, #pr-title') | |||
|
990 | self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save') | |||
|
991 | }, | |||
|
992 | ||||
|
993 | init: function () { | |||
|
994 | var self = this; | |||
|
995 | self.setSelectors(); | |||
|
996 | self.editButton.on('click', function (e) { | |||
|
997 | self.edit(); | |||
|
998 | }); | |||
|
999 | self.closeButton.on('click', function (e) { | |||
|
1000 | self.view(); | |||
|
1001 | }); | |||
|
1002 | }, | |||
|
1003 | ||||
|
1004 | edit: function (event) { | |||
|
1005 | var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm; | |||
|
1006 | this.viewFields.hide(); | |||
|
1007 | this.editButton.hide(); | |||
|
1008 | this.deleteButton.hide(); | |||
|
1009 | this.closeButton.show(); | |||
|
1010 | this.editFields.show(); | |||
|
1011 | cmInstance.refresh(); | |||
|
1012 | }, | |||
|
1013 | ||||
|
1014 | view: function (event) { | |||
|
1015 | this.editButton.show(); | |||
|
1016 | this.deleteButton.show(); | |||
|
1017 | this.editFields.hide(); | |||
|
1018 | this.closeButton.hide(); | |||
|
1019 | this.viewFields.show(); | |||
|
1020 | } | |||
|
1021 | }; | |||
746 |
|
1022 | |||
747 | /** |
|
1023 | /** | |
748 | * OnLine presence using channelstream |
|
1024 | * OnLine presence using channelstream | |
@@ -813,17 +1089,14 b' window.refreshComments = function (versi' | |||||
813 | $.each($('.comment'), function (idx, element) { |
|
1089 | $.each($('.comment'), function (idx, element) { | |
814 | currentIDs.push($(element).data('commentId')); |
|
1090 | currentIDs.push($(element).data('commentId')); | |
815 | }); |
|
1091 | }); | |
816 |
var data = {"comments |
|
1092 | var data = {"comments": currentIDs}; | |
817 |
|
1093 | |||
818 | var $targetElem = $('.comments-content-table'); |
|
1094 | var $targetElem = $('.comments-content-table'); | |
819 | $targetElem.css('opacity', 0.3); |
|
1095 | $targetElem.css('opacity', 0.3); | |
820 | $targetElem.load( |
|
1096 | ||
821 | loadUrl, data, function (responseText, textStatus, jqXHR) { |
|
1097 | var success = function (data) { | |
822 | if (jqXHR.status !== 200) { |
|
|||
823 | return false; |
|
|||
824 | } |
|
|||
825 |
|
|
1098 | var $counterElem = $('#comments-count'); | |
826 |
|
|
1099 | var newCount = $(data).data('counter'); | |
827 |
|
|
1100 | if (newCount !== undefined) { | |
828 |
|
|
1101 | var callback = function () { | |
829 |
|
|
1102 | $counterElem.animate({'opacity': 1.00}, 200) | |
@@ -833,9 +1106,12 b' window.refreshComments = function (versi' | |||||
833 |
|
|
1106 | } | |
834 |
|
1107 | |||
835 |
|
|
1108 | $targetElem.css('opacity', 1); | |
|
1109 | $targetElem.html(data); | |||
836 |
|
|
1110 | tooltipActivate(); | |
837 |
|
|
1111 | } | |
838 | ); |
|
1112 | ||
|
1113 | ajaxPOST(loadUrl, data, success, null, {}) | |||
|
1114 | ||||
839 | } |
|
1115 | } | |
840 |
|
1116 | |||
841 | window.refreshTODOs = function (version) { |
|
1117 | window.refreshTODOs = function (version) { | |
@@ -858,16 +1134,13 b' window.refreshTODOs = function (version)' | |||||
858 | currentIDs.push($(element).data('commentId')); |
|
1134 | currentIDs.push($(element).data('commentId')); | |
859 | }); |
|
1135 | }); | |
860 |
|
1136 | |||
861 |
var data = {"comments |
|
1137 | var data = {"comments": currentIDs}; | |
862 | var $targetElem = $('.todos-content-table'); |
|
1138 | var $targetElem = $('.todos-content-table'); | |
863 | $targetElem.css('opacity', 0.3); |
|
1139 | $targetElem.css('opacity', 0.3); | |
864 | $targetElem.load( |
|
1140 | ||
865 | loadUrl, data, function (responseText, textStatus, jqXHR) { |
|
1141 | var success = function (data) { | |
866 | if (jqXHR.status !== 200) { |
|
|||
867 | return false; |
|
|||
868 | } |
|
|||
869 |
|
|
1142 | var $counterElem = $('#todos-count') | |
870 |
|
|
1143 | var newCount = $(data).data('counter'); | |
871 |
|
|
1144 | if (newCount !== undefined) { | |
872 |
|
|
1145 | var callback = function () { | |
873 |
|
|
1146 | $counterElem.animate({'opacity': 1.00}, 200) | |
@@ -877,9 +1150,12 b' window.refreshTODOs = function (version)' | |||||
877 |
|
|
1150 | } | |
878 |
|
1151 | |||
879 |
|
|
1152 | $targetElem.css('opacity', 1); | |
|
1153 | $targetElem.html(data); | |||
880 |
|
|
1154 | tooltipActivate(); | |
881 |
|
|
1155 | } | |
882 | ); |
|
1156 | ||
|
1157 | ajaxPOST(loadUrl, data, success, null, {}) | |||
|
1158 | ||||
883 | } |
|
1159 | } | |
884 |
|
1160 | |||
885 | window.refreshAllComments = function (version) { |
|
1161 | window.refreshAllComments = function (version) { | |
@@ -888,3 +1164,12 b' window.refreshAllComments = function (ve' | |||||
888 | refreshComments(version); |
|
1164 | refreshComments(version); | |
889 | refreshTODOs(version); |
|
1165 | refreshTODOs(version); | |
890 | }; |
|
1166 | }; | |
|
1167 | ||||
|
1168 | window.sidebarComment = function (commentId) { | |||
|
1169 | var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64'); | |||
|
1170 | if (!jsonData) { | |||
|
1171 | return 'Failed to load comment {0}'.format(commentId) | |||
|
1172 | } | |||
|
1173 | var funcData = JSON.parse(atob(jsonData)); | |||
|
1174 | return renderTemplate('sideBarCommentHovercard', funcData) | |||
|
1175 | }; |
@@ -57,15 +57,18 b' var ajaxGET = function (url, success, fa' | |||||
57 | return request; |
|
57 | return request; | |
58 | }; |
|
58 | }; | |
59 |
|
59 | |||
60 | var ajaxPOST = function (url, postData, success, failure) { |
|
60 | var ajaxPOST = function (url, postData, success, failure, options) { | |
61 | var sUrl = url; |
|
61 | ||
62 | var postData = toQueryString(postData); |
|
62 | var ajaxSettings = $.extend({ | |
63 | var request = $.ajax({ |
|
|||
64 | type: 'POST', |
|
63 | type: 'POST', | |
65 |
url: |
|
64 | url: url, | |
66 | data: postData, |
|
65 | data: toQueryString(postData), | |
67 | headers: {'X-PARTIAL-XHR': true} |
|
66 | headers: {'X-PARTIAL-XHR': true} | |
68 | }) |
|
67 | }, options); | |
|
68 | ||||
|
69 | var request = $.ajax( | |||
|
70 | ajaxSettings | |||
|
71 | ) | |||
69 | .done(function (data) { |
|
72 | .done(function (data) { | |
70 | success(data); |
|
73 | success(data); | |
71 | }) |
|
74 | }) | |
@@ -126,7 +129,8 b' function formatErrorMessage(jqXHR, textS' | |||||
126 | } else if (errorThrown === 'abort') { |
|
129 | } else if (errorThrown === 'abort') { | |
127 | return (prefix + 'Ajax request aborted.'); |
|
130 | return (prefix + 'Ajax request aborted.'); | |
128 | } else { |
|
131 | } else { | |
129 |
r |
|
132 | var errInfo = 'Uncaught Error. code: {0}\n'.format(jqXHR.status) | |
|
133 | return (prefix + errInfo + jqXHR.responseText); | |||
130 | } |
|
134 | } | |
131 | } |
|
135 | } | |
132 |
|
136 |
@@ -89,36 +89,41 b'' | |||||
89 | if is_pr: |
|
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') |
|
90 | version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version') | |
91 | %> |
|
91 | %> | |
92 |
|
92 | ## NEW, since refresh | ||
93 | <script type="text/javascript"> |
|
93 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |
94 | // closure function helper |
|
94 | <div class="tooltip" style="position: absolute; left: 8px" title="New comment"> | |
95 | var sidebarComment${comment_obj.comment_id} = function() { |
|
95 | ! | |
96 | return renderTemplate('sideBarCommentHovercard', { |
|
96 | </div> | |
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 |
|
97 | % endif | |
117 |
|
|
98 | ||
118 |
|
|
99 | <% | |
119 | % if existing_ids and comment_obj.comment_id not in existing_ids: |
|
100 | data = h.json.dumps({ | |
120 | <span class="tag">NEW</span> |
|
101 | 'comment_id': comment_obj.comment_id, | |
121 | % endif |
|
102 | 'version_info': version_info, | |
|
103 | 'file_name': comment_obj.f_path, | |||
|
104 | 'line_no': comment_obj.line_no, | |||
|
105 | 'outdated': comment_obj.outdated, | |||
|
106 | 'inline': comment_obj.is_inline, | |||
|
107 | 'is_todo': comment_obj.is_todo, | |||
|
108 | 'created_on': h.format_date(comment_obj.created_on), | |||
|
109 | 'datetime': '{}{}'.format(comment_obj.created_on, h.get_timezone(comment_obj.created_on, time_is_local=True)), | |||
|
110 | 'review_status': (comment_obj.review_status or '') | |||
|
111 | }) | |||
|
112 | ||||
|
113 | if comment_obj.outdated: | |||
|
114 | icon = 'icon-comment-toggle' | |||
|
115 | elif comment_obj.is_inline: | |||
|
116 | icon = 'icon-code' | |||
|
117 | else: | |||
|
118 | icon = 'icon-comment' | |||
|
119 | %> | |||
|
120 | ||||
|
121 | <i id="commentHovercard${comment_obj.comment_id}" | |||
|
122 | class="${icon} tooltip-hovercard" | |||
|
123 | data-hovercard-url="javascript:sidebarComment(${comment_obj.comment_id})" | |||
|
124 | data-comment-json-b64='${h.b64(data)}'> | |||
|
125 | </i> | |||
|
126 | ||||
122 | </td> |
|
127 | </td> | |
123 |
|
128 | |||
124 | <td class="td-todo-gravatar"> |
|
129 | <td class="td-todo-gravatar"> |
@@ -187,12 +187,12 b'' | |||||
187 | <div class="sidebar-element clear-both"> |
|
187 | <div class="sidebar-element clear-both"> | |
188 | <% vote_title = _ungettext( |
|
188 | <% vote_title = _ungettext( | |
189 | 'Status calculated based on votes from {} reviewer', |
|
189 | 'Status calculated based on votes from {} reviewer', | |
190 |
'Status calculated based on votes from {} reviewers', |
|
190 | 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count) | |
191 | %> |
|
191 | %> | |
192 |
|
192 | |||
193 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> |
|
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> |
|
194 | <i class="icon-circle review-status-${c.commit_review_status}"></i> | |
195 |
${ |
|
195 | ${c.reviewers_count} | |
196 | </div> |
|
196 | </div> | |
197 | </div> |
|
197 | </div> | |
198 |
|
198 |
@@ -149,7 +149,7 b'' | |||||
149 | <span class="user"> <a href="/_profiles/jenkins-tests">jenkins-tests</a> (reviewer)</span> |
|
149 | <span class="user"> <a href="/_profiles/jenkins-tests">jenkins-tests</a> (reviewer)</span> | |
150 | </div> |
|
150 | </div> | |
151 | <input id="reviewer_70_input" type="hidden" value="70" name="review_members"> |
|
151 | <input id="reviewer_70_input" type="hidden" value="70" name="review_members"> | |
152 |
<div class="reviewer_member_remove action_button" onclick="remove |
|
152 | <div class="reviewer_member_remove action_button" onclick="removeMember(70, true)" style="visibility: hidden;"> | |
153 | <i class="icon-remove"></i> |
|
153 | <i class="icon-remove"></i> | |
154 | </div> |
|
154 | </div> | |
155 | </li> |
|
155 | </li> |
@@ -66,9 +66,18 b" var data_hovercard_url = pyroutes.url('h" | |||||
66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> |
|
66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> | |
67 |
|
67 | |||
68 | <td style="width: 20px"> |
|
68 | <td style="width: 20px"> | |
|
69 | <div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page"> | |||
|
70 | <i class="icon-eye" style="color: #0ac878"></i> | |||
|
71 | </div> | |||
|
72 | <% if (role === 'reviewer') { %> | |||
69 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> |
|
73 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> | |
70 | <i class="icon-circle review-status-<%= review_status %>"></i> |
|
74 | <i class="icon-circle review-status-<%= review_status %>"></i> | |
71 | </div> |
|
75 | </div> | |
|
76 | <% } else if (role === 'observer') { %> | |||
|
77 | <div class="tooltip" title="Observer without voting right."> | |||
|
78 | <i class="icon-circle-thin"></i> | |||
|
79 | </div> | |||
|
80 | <% } %> | |||
72 |
|
|
81 | </td> | |
73 |
|
82 | |||
74 | <td> |
|
83 | <td> | |
@@ -84,9 +93,6 b" var data_hovercard_url = pyroutes.url('h" | |||||
84 | 'gravatar_url': member.gravatar_link |
|
93 | 'gravatar_url': member.gravatar_link | |
85 | }) |
|
94 | }) | |
86 | %> |
|
95 | %> | |
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> |
|
|||
90 |
|
|
96 | </div> | |
91 | </td> |
|
97 | </td> | |
92 |
|
98 | |||
@@ -108,7 +114,7 b" var data_hovercard_url = pyroutes.url('h" | |||||
108 | <% } else { %> |
|
114 | <% } else { %> | |
109 | <td style="text-align: right;width: 10px;"> |
|
115 | <td style="text-align: right;width: 10px;"> | |
110 | <% if (allowed_to_update) { %> |
|
116 | <% if (allowed_to_update) { %> | |
111 |
<div class=" |
|
117 | <div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;"> | |
112 | <i class="icon-remove"></i> |
|
118 | <i class="icon-remove"></i> | |
113 | </div> |
|
119 | </div> | |
114 | <% } %> |
|
120 | <% } %> | |
@@ -117,7 +123,7 b" var data_hovercard_url = pyroutes.url('h" | |||||
117 |
|
123 | |||
118 | </tr> |
|
124 | </tr> | |
119 |
|
125 | |||
120 | <tr> |
|
126 | <tr id="reviewer_<%= member.user_id %>_rules"> | |
121 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> |
|
127 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> | |
122 | <input type="hidden" name="__start__" value="reviewer:mapping"> |
|
128 | <input type="hidden" name="__start__" value="reviewer:mapping"> | |
123 |
|
129 | |||
@@ -149,6 +155,7 b" var data_hovercard_url = pyroutes.url('h" | |||||
149 |
|
155 | |||
150 | <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" /> |
|
156 | <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" /> | |
151 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> |
|
157 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> | |
|
158 | <input type="hidden" name="role" value="<%= role %>"/> | |||
152 |
|
159 | |||
153 | <input type="hidden" name="__end__" value="reviewer:mapping"> |
|
160 | <input type="hidden" name="__end__" value="reviewer:mapping"> | |
154 | </td> |
|
161 | </td> |
@@ -11,6 +11,9 b' data = {' | |||||
11 | 'pr_title': pull_request.title, |
|
11 | 'pr_title': pull_request.title, | |
12 | } |
|
12 | } | |
13 |
|
13 | |||
|
14 | if user_role == 'observer': | |||
|
15 | subject_template = email_pr_review_subject_template or _('{user} added you as observer to pull request. !{pr_id}: "{pr_title}"') | |||
|
16 | else: | |||
14 | subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"') |
|
17 | subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"') | |
15 | %> |
|
18 | %> | |
16 |
|
19 | |||
@@ -34,6 +37,7 b' data = {' | |||||
34 | 'source_repo_url': pull_request_source_repo_url, |
|
37 | 'source_repo_url': pull_request_source_repo_url, | |
35 | 'target_repo_url': pull_request_target_repo_url, |
|
38 | 'target_repo_url': pull_request_target_repo_url, | |
36 | } |
|
39 | } | |
|
40 | ||||
37 | %> |
|
41 | %> | |
38 |
|
42 | |||
39 | * ${_('Pull Request link')}: ${pull_request_url} |
|
43 | * ${_('Pull Request link')}: ${pull_request_url} | |
@@ -51,7 +55,7 b' data = {' | |||||
51 |
|
55 | |||
52 | % for commit_id, message in pull_request_commits: |
|
56 | % for commit_id, message in pull_request_commits: | |
53 | - ${h.short_id(commit_id)} |
|
57 | - ${h.short_id(commit_id)} | |
54 |
|
|
58 | ${h.chop_at_smart(message.lstrip(), '\n', suffix_if_chopped='...')} | |
55 |
|
59 | |||
56 | % endfor |
|
60 | % endfor | |
57 |
|
61 | |||
@@ -78,19 +82,23 b' data = {' | |||||
78 | <table style="text-align:left;vertical-align:middle;width: 100%"> |
|
82 | <table style="text-align:left;vertical-align:middle;width: 100%"> | |
79 | <tr> |
|
83 | <tr> | |
80 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> |
|
84 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> | |
81 |
|
||||
82 | <div style="margin: 0; font-weight: bold"> |
|
85 | <div style="margin: 0; font-weight: bold"> | |
|
86 | % if user_role == 'observer': | |||
|
87 | <div class="clear-both" class="clear-both" style="margin-bottom: 4px"> | |||
|
88 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> | |||
|
89 | ${_('added you as observer to')} | |||
|
90 | <a href="${pull_request_url}" style="${base.link_css()}">pull request</a>. | |||
|
91 | </div> | |||
|
92 | % else: | |||
83 | <div class="clear-both" class="clear-both" style="margin-bottom: 4px"> |
|
93 | <div class="clear-both" class="clear-both" style="margin-bottom: 4px"> | |
84 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> |
|
94 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> | |
85 | ${_('requested a')} |
|
95 | ${_('requested a')} | |
86 | <a href="${pull_request_url}" style="${base.link_css()}"> |
|
96 | <a href="${pull_request_url}" style="${base.link_css()}">pull request</a> review. | |
87 | ${_('pull request review.').format(**data) } |
|
|||
88 | </a> |
|
|||
89 | </div> |
|
97 | </div> | |
|
98 | % endif | |||
90 | <div style="margin-top: 10px"></div> |
|
99 | <div style="margin-top: 10px"></div> | |
91 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> |
|
100 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> | |
92 | </div> |
|
101 | </div> | |
93 |
|
||||
94 | </td> |
|
102 | </td> | |
95 | </tr> |
|
103 | </tr> | |
96 |
|
104 |
@@ -112,7 +112,7 b'' | |||||
112 | ## REVIEWERS |
|
112 | ## REVIEWERS | |
113 | <div class="field"> |
|
113 | <div class="field"> | |
114 | <div class="label label-textarea"> |
|
114 | <div class="label label-textarea"> | |
115 | <label for="pullrequest_reviewers">${_('Reviewers')}:</label> |
|
115 | <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label> | |
116 | </div> |
|
116 | </div> | |
117 | <div class="content"> |
|
117 | <div class="content"> | |
118 | ## REVIEW RULES |
|
118 | ## REVIEW RULES | |
@@ -125,18 +125,40 b'' | |||||
125 | </div> |
|
125 | </div> | |
126 | </div> |
|
126 | </div> | |
127 |
|
127 | |||
128 | ## REVIEWERS |
|
128 | ## REVIEWERS / OBSERVERS | |
129 | <div class="reviewers-title"> |
|
129 | <div class="reviewers-title"> | |
130 | <div class="pr-details-title"> |
|
130 | ||
131 |
|
|
131 | <ul class="nav-links clearfix"> | |
132 | <span class="calculate-reviewers"> - ${_('loading...')}</span> |
|
132 | ||
133 |
|
|
133 | ## TAB1 MANDATORY REVIEWERS | |
134 |
< |
|
134 | <li class="active"> | |
|
135 | <a id="reviewers-btn" href="#showReviewers" tabindex="-1"> | |||
|
136 | Reviewers | |||
|
137 | <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span> | |||
|
138 | </a> | |||
|
139 | </li> | |||
|
140 | ||||
|
141 | ## TAB2 OBSERVERS | |||
|
142 | <li class=""> | |||
|
143 | <a id="observers-btn" href="#showObservers" tabindex="-1"> | |||
|
144 | Observers | |||
|
145 | <span id="observers-cnt" data-count="0" class="menulink-counter">0</span> | |||
|
146 | </a> | |||
|
147 | </li> | |||
|
148 | ||||
|
149 | </ul> | |||
|
150 | ||||
|
151 | ## TAB1 MANDATORY REVIEWERS | |||
|
152 | <div id="reviewers-container"> | |||
|
153 | <span class="calculate-reviewers"> | |||
|
154 | <h4>${_('loading...')}</h4> | |||
|
155 | </span> | |||
|
156 | ||||
135 | <div id="reviewers" class="pr-details-content reviewers"> |
|
157 | <div id="reviewers" class="pr-details-content reviewers"> | |
136 | ## members goes here, filled via JS based on initial selection ! |
|
158 | ## members goes here, filled via JS based on initial selection ! | |
137 | <input type="hidden" name="__start__" value="review_members:sequence"> |
|
159 | <input type="hidden" name="__start__" value="review_members:sequence"> | |
138 | <table id="review_members" class="group_members"> |
|
160 | <table id="review_members" class="group_members"> | |
139 | ## This content is loaded via JS and ReviewersPanel |
|
161 | ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element | |
140 | </table> |
|
162 | </table> | |
141 | <input type="hidden" name="__end__" value="review_members:sequence"> |
|
163 | <input type="hidden" name="__end__" value="review_members:sequence"> | |
142 |
|
164 | |||
@@ -149,6 +171,34 b'' | |||||
149 |
|
171 | |||
150 | </div> |
|
172 | </div> | |
151 | </div> |
|
173 | </div> | |
|
174 | ||||
|
175 | ## TAB2 OBSERVERS | |||
|
176 | <div id="observers-container" style="display: none"> | |||
|
177 | <span class="calculate-reviewers"> | |||
|
178 | <h4>${_('loading...')}</h4> | |||
|
179 | </span> | |||
|
180 | ||||
|
181 | <div id="observers" class="pr-details-content observers"> | |||
|
182 | ## members goes here, filled via JS based on initial selection ! | |||
|
183 | <input type="hidden" name="__start__" value="observer_members:sequence"> | |||
|
184 | <table id="observer_members" class="group_members"> | |||
|
185 | ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element | |||
|
186 | </table> | |||
|
187 | <input type="hidden" name="__end__" value="observer_members:sequence"> | |||
|
188 | ||||
|
189 | <div id="add_observer_input" class='ac'> | |||
|
190 | <div class="observer_ac"> | |||
|
191 | ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))} | |||
|
192 | <div id="observers_container"></div> | |||
|
193 | </div> | |||
|
194 | </div> | |||
|
195 | </div> | |||
|
196 | ||||
|
197 | </div> | |||
|
198 | ||||
|
199 | </div> | |||
|
200 | ||||
|
201 | </div> | |||
152 | </div> |
|
202 | </div> | |
153 |
|
203 | |||
154 | ## SUBMIT |
|
204 | ## SUBMIT | |
@@ -339,7 +389,6 b'' | |||||
339 |
|
389 | |||
340 | //make both panels equal |
|
390 | //make both panels equal | |
341 | $('.target-panel').height($('.source-panel').height()) |
|
391 | $('.target-panel').height($('.source-panel').height()) | |
342 |
|
||||
343 | }; |
|
392 | }; | |
344 |
|
393 | |||
345 | reviewersController = new ReviewersController(); |
|
394 | reviewersController = new ReviewersController(); | |
@@ -465,8 +514,7 b'' | |||||
465 | queryTargetRefs(initialData, query) |
|
514 | queryTargetRefs(initialData, query) | |
466 | }, |
|
515 | }, | |
467 | initSelection: initRefSelection() |
|
516 | initSelection: initRefSelection() | |
468 |
|
|
517 | }); | |
469 | ); |
|
|||
470 |
|
518 | |||
471 | var sourceRepoSelect2 = Select2Box($sourceRepo, { |
|
519 | var sourceRepoSelect2 = Select2Box($sourceRepo, { | |
472 | query: function(query) {} |
|
520 | query: function(query) {} | |
@@ -543,12 +591,44 b'' | |||||
543 | $sourceRef.select2('val', '${c.default_source_ref}'); |
|
591 | $sourceRef.select2('val', '${c.default_source_ref}'); | |
544 |
|
592 | |||
545 |
|
593 | |||
546 | // default reviewers |
|
594 | // default reviewers / observers | |
547 | reviewersController.loadDefaultReviewers( |
|
595 | reviewersController.loadDefaultReviewers( | |
548 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
596 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); | |
549 | % endif |
|
597 | % endif | |
550 |
|
598 | |||
551 | ReviewerAutoComplete('#user'); |
|
599 | ReviewerAutoComplete('#user', reviewersController); | |
|
600 | ObserverAutoComplete('#observer', reviewersController); | |||
|
601 | ||||
|
602 | // TODO, move this to another handler | |||
|
603 | ||||
|
604 | var $reviewersBtn = $('#reviewers-btn'); | |||
|
605 | var $reviewersContainer = $('#reviewers-container'); | |||
|
606 | ||||
|
607 | var $observersBtn = $('#observers-btn') | |||
|
608 | var $observersContainer = $('#observers-container'); | |||
|
609 | ||||
|
610 | $reviewersBtn.on('click', function (e) { | |||
|
611 | ||||
|
612 | $observersContainer.hide(); | |||
|
613 | $reviewersContainer.show(); | |||
|
614 | ||||
|
615 | $observersBtn.parent().removeClass('active'); | |||
|
616 | $reviewersBtn.parent().addClass('active'); | |||
|
617 | e.preventDefault(); | |||
|
618 | ||||
|
619 | }) | |||
|
620 | ||||
|
621 | $observersBtn.on('click', function (e) { | |||
|
622 | ||||
|
623 | $reviewersContainer.hide(); | |||
|
624 | $observersContainer.show(); | |||
|
625 | ||||
|
626 | $reviewersBtn.parent().removeClass('active'); | |||
|
627 | $observersBtn.parent().addClass('active'); | |||
|
628 | e.preventDefault(); | |||
|
629 | ||||
|
630 | }) | |||
|
631 | ||||
552 | }); |
|
632 | }); | |
553 | </script> |
|
633 | </script> | |
554 |
|
634 |
@@ -556,12 +556,12 b'' | |||||
556 | <div class="sidebar-element clear-both"> |
|
556 | <div class="sidebar-element clear-both"> | |
557 | <% vote_title = _ungettext( |
|
557 | <% vote_title = _ungettext( | |
558 | 'Status calculated based on votes from {} reviewer', |
|
558 | 'Status calculated based on votes from {} reviewer', | |
559 |
'Status calculated based on votes from {} reviewers', |
|
559 | 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count) | |
560 | %> |
|
560 | %> | |
561 |
|
561 | |||
562 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> |
|
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> |
|
563 | <i class="icon-circle review-status-${c.pull_request_review_status}"></i> | |
564 |
${ |
|
564 | ${c.reviewers_count} | |
565 | </div> |
|
565 | </div> | |
566 |
|
566 | |||
567 | ## REVIEW RULES |
|
567 | ## REVIEW RULES | |
@@ -609,13 +609,13 b'' | |||||
609 | <div id="add_reviewer" class="ac" style="display: none;"> |
|
609 | <div id="add_reviewer" class="ac" style="display: none;"> | |
610 | %if c.allowed_to_update: |
|
610 | %if c.allowed_to_update: | |
611 | % if not c.forbid_adding_reviewers: |
|
611 | % if not c.forbid_adding_reviewers: | |
612 | <div id="add_reviewer_input" class="reviewer_ac"> |
|
612 | <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px"> | |
613 |
|
|
613 | <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off"> | |
614 | <div id="reviewers_container"></div> |
|
614 | <div id="reviewers_container"></div> | |
615 | </div> |
|
615 | </div> | |
616 | % endif |
|
616 | % endif | |
617 | <div class="pull-right"> |
|
617 | <div class="pull-right" style="margin-bottom: 15px"> | |
618 |
<button id="update_ |
|
618 | <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button> | |
619 | </div> |
|
619 | </div> | |
620 | %endif |
|
620 | %endif | |
621 | </div> |
|
621 | </div> | |
@@ -623,23 +623,52 b'' | |||||
623 | </div> |
|
623 | </div> | |
624 | </div> |
|
624 | </div> | |
625 |
|
625 | |||
626 |
|
|
626 | ## OBSERVERS | |
627 |
|
|
627 | <div class="sidebar-element clear-both"> | |
628 |
|
|
628 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}"> | |
629 |
|
|
629 | <i class="icon-circle-thin"></i> | |
630 |
|
|
630 | ${c.observers_count} | |
631 |
|
|
631 | </div> | |
632 | ## |
|
632 | ||
633 |
|
|
633 | <div class="right-sidebar-expanded-state pr-details-title"> | |
634 |
|
|
634 | <span class="sidebar-heading"> | |
635 |
|
|
635 | <i class="icon-circle-thin"></i> | |
636 |
|
|
636 | ${_('Observers')} | |
637 |
|
|
637 | </span> | |
638 | ## </div> |
|
638 | %if c.allowed_to_update: | |
639 | ## <div class="right-sidebar-expanded-state pr-details-content"> |
|
639 | <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span> | |
640 | ## No observers |
|
640 | <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> | |
641 |
|
|
641 | %endif | |
642 |
|
|
642 | </div> | |
|
643 | ||||
|
644 | <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers"> | |||
|
645 | ## members redering block | |||
|
646 | <input type="hidden" name="__start__" value="observer_members:sequence"> | |||
|
647 | ||||
|
648 | <table id="observer_members" class="group_members"> | |||
|
649 | ## This content is loaded via JS and ReviewersPanel | |||
|
650 | </table> | |||
|
651 | ||||
|
652 | <input type="hidden" name="__end__" value="observer_members:sequence"> | |||
|
653 | ## end members redering block | |||
|
654 | ||||
|
655 | %if not c.pull_request.is_closed(): | |||
|
656 | <div id="add_observer" class="ac" style="display: none;"> | |||
|
657 | %if c.allowed_to_update: | |||
|
658 | % if not c.forbid_adding_reviewers or 1: | |||
|
659 | <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" > | |||
|
660 | <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off"> | |||
|
661 | <div id="observers_container"></div> | |||
|
662 | </div> | |||
|
663 | % endif | |||
|
664 | <div class="pull-right" style="margin-bottom: 15px"> | |||
|
665 | <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button> | |||
|
666 | </div> | |||
|
667 | %endif | |||
|
668 | </div> | |||
|
669 | %endif | |||
|
670 | </div> | |||
|
671 | </div> | |||
643 |
|
|
672 | ||
644 | ## TODOs |
|
673 | ## TODOs | |
645 |
|
|
674 | <div class="sidebar-element clear-both"> | |
@@ -815,6 +844,7 b' updateController = new UpdatePrControlle' | |||||
815 |
|
844 | |||
816 | window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n}; |
|
845 | window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n}; | |
817 | window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n}; |
|
846 | window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n}; | |
|
847 | window.setObserversData = ${c.pull_request_set_observers_data_json | n}; | |||
818 |
|
848 | |||
819 | (function () { |
|
849 | (function () { | |
820 | "use strict"; |
|
850 | "use strict"; | |
@@ -822,44 +852,9 b' window.setReviewersData = ${c.pull_reque' | |||||
822 | // custom code mirror |
|
852 | // custom code mirror | |
823 | var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm; |
|
853 | var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm; | |
824 |
|
854 | |||
825 | var PRDetails = { |
|
|||
826 | editButton: $('#open_edit_pullrequest'), |
|
|||
827 | closeButton: $('#close_edit_pullrequest'), |
|
|||
828 | deleteButton: $('#delete_pullrequest'), |
|
|||
829 | viewFields: $('#pr-desc, #pr-title'), |
|
|||
830 | editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'), |
|
|||
831 |
|
||||
832 | init: function () { |
|
|||
833 | var that = this; |
|
|||
834 | this.editButton.on('click', function (e) { |
|
|||
835 | that.edit(); |
|
|||
836 | }); |
|
|||
837 | this.closeButton.on('click', function (e) { |
|
|||
838 | that.view(); |
|
|||
839 | }); |
|
|||
840 | }, |
|
|||
841 |
|
||||
842 | edit: function (event) { |
|
|||
843 | var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm; |
|
|||
844 | this.viewFields.hide(); |
|
|||
845 | this.editButton.hide(); |
|
|||
846 | this.deleteButton.hide(); |
|
|||
847 | this.closeButton.show(); |
|
|||
848 | this.editFields.show(); |
|
|||
849 | cmInstance.refresh(); |
|
|||
850 | }, |
|
|||
851 |
|
||||
852 | view: function (event) { |
|
|||
853 | this.editButton.show(); |
|
|||
854 | this.deleteButton.show(); |
|
|||
855 | this.editFields.hide(); |
|
|||
856 | this.closeButton.hide(); |
|
|||
857 | this.viewFields.show(); |
|
|||
858 | } |
|
|||
859 | }; |
|
|||
860 |
|
||||
861 | PRDetails.init(); |
|
855 | PRDetails.init(); | |
862 | ReviewersPanel.init(reviewerRulesData, setReviewersData); |
|
856 | ReviewersPanel.init(reviewerRulesData, setReviewersData); | |
|
857 | ObserversPanel.init(reviewerRulesData, setObserversData); | |||
863 |
|
858 | |||
864 | window.showOutdated = function (self) { |
|
859 | window.showOutdated = function (self) { | |
865 | $('.comment-inline.comment-outdated').show(); |
|
860 | $('.comment-inline.comment-outdated').show(); | |
@@ -929,12 +924,17 b' window.setReviewersData = ${c.pull_reque' | |||||
929 | title, description, renderer); |
|
924 | title, description, renderer); | |
930 | }); |
|
925 | }); | |
931 |
|
926 | |||
932 | $('#update_pull_request').on('click', function (e) { |
|
927 | var $updateButtons = $('#update_reviewers,#update_observers'); | |
933 | $(this).attr('disabled', 'disabled'); |
|
928 | $updateButtons.on('click', function (e) { | |
934 |
$(this). |
|
929 | var role = $(this).data('role'); | |
935 | $(this).html(_gettext('Saving...')); |
|
930 | $updateButtons.attr('disabled', 'disabled'); | |
|
931 | $updateButtons.addClass('disabled'); | |||
|
932 | $updateButtons.html(_gettext('Saving...')); | |||
936 | reviewersController.updateReviewers( |
|
933 | reviewersController.updateReviewers( | |
937 | "${c.repo_name}", "${c.pull_request.pull_request_id}"); |
|
934 | templateContext.repo_name, | |
|
935 | templateContext.pull_request_data.pull_request_id, | |||
|
936 | role | |||
|
937 | ); | |||
938 | }); |
|
938 | }); | |
939 |
|
939 | |||
940 | // fixing issue with caches on firefox |
|
940 | // fixing issue with caches on firefox | |
@@ -978,7 +978,8 b' window.setReviewersData = ${c.pull_reque' | |||||
978 | refreshMergeChecks(); |
|
978 | refreshMergeChecks(); | |
979 | }; |
|
979 | }; | |
980 |
|
980 | |||
981 | ReviewerAutoComplete('#user'); |
|
981 | ReviewerAutoComplete('#user', reviewersController); | |
|
982 | ObserverAutoComplete('#observer', reviewersController); | |||
982 |
|
983 | |||
983 | })(); |
|
984 | })(); | |
984 |
|
985 |
@@ -23,7 +23,7 b' import collections' | |||||
23 |
|
23 | |||
24 | from rhodecode.lib.partial_renderer import PyramidPartialRenderer |
|
24 | from rhodecode.lib.partial_renderer import PyramidPartialRenderer | |
25 | from rhodecode.lib.utils2 import AttributeDict |
|
25 | from rhodecode.lib.utils2 import AttributeDict | |
26 | from rhodecode.model.db import User |
|
26 | from rhodecode.model.db import User, PullRequestReviewers | |
27 | from rhodecode.model.notification import EmailNotificationModel |
|
27 | from rhodecode.model.notification import EmailNotificationModel | |
28 |
|
28 | |||
29 |
|
29 | |||
@@ -52,7 +52,8 b' def test_render_email(app, http_host_onl' | |||||
52 | assert 'Email Body' in body |
|
52 | assert 'Email Body' in body | |
53 |
|
53 | |||
54 |
|
54 | |||
55 | def test_render_pr_email(app, user_admin): |
|
55 | @pytest.mark.parametrize('role', PullRequestReviewers.ROLES) | |
|
56 | def test_render_pr_email(app, user_admin, role): | |||
56 | ref = collections.namedtuple( |
|
57 | ref = collections.namedtuple( | |
57 | 'Ref', 'name, type')('fxies123', 'book') |
|
58 | 'Ref', 'name, type')('fxies123', 'book') | |
58 |
|
59 | |||
@@ -75,13 +76,17 b' def test_render_pr_email(app, user_admin' | |||||
75 | 'pull_request_source_repo_url': 'x', |
|
76 | 'pull_request_source_repo_url': 'x', | |
76 |
|
77 | |||
77 | 'pull_request_url': 'http://localhost/pr1', |
|
78 | 'pull_request_url': 'http://localhost/pr1', | |
|
79 | 'user_role': role, | |||
78 | } |
|
80 | } | |
79 |
|
81 | |||
80 | subject, body, body_plaintext = EmailNotificationModel().render_email( |
|
82 | subject, body, body_plaintext = EmailNotificationModel().render_email( | |
81 | EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs) |
|
83 | EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs) | |
82 |
|
84 | |||
83 | # subject |
|
85 | # subject | |
|
86 | if role == PullRequestReviewers.ROLE_REVIEWER: | |||
84 | assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"' |
|
87 | assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"' | |
|
88 | elif role == PullRequestReviewers.ROLE_OBSERVER: | |||
|
89 | assert subject == '@test_admin (RhodeCode Admin) added you as observer to pull request. !200: "Example Pull Request"' | |||
85 |
|
90 | |||
86 |
|
91 | |||
87 | def test_render_pr_update_email(app, user_admin): |
|
92 | def test_render_pr_update_email(app, user_admin): |
General Comments 0
You need to be logged in to leave comments.
Login now