Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,54 b'' | |||
|
1 | |RCE| 4.21.0 |RNS| | |
|
2 | ------------------ | |
|
3 | ||
|
4 | Release Date | |
|
5 | ^^^^^^^^^^^^ | |
|
6 | ||
|
7 | - 2020-09-28 | |
|
8 | ||
|
9 | ||
|
10 | New Features | |
|
11 | ^^^^^^^^^^^^ | |
|
12 | ||
|
13 | - Pull requests: overhaul of the UX/UI by adding new sidebar | |
|
14 | - Pull requests: new live reviewer present indicator (requires channelstream enabled) | |
|
15 | - Pull requests: new live new comments indicator (requires channelstream enabled) | |
|
16 | - Pull requests: new sidebar with comments/todos/referenced tickets navigation | |
|
17 | - Commits page: Introduced sidebar for single commits pages | |
|
18 | ||
|
19 | ||
|
20 | General | |
|
21 | ^^^^^^^ | |
|
22 | ||
|
23 | - API: allow repo admins to get/set settings. | |
|
24 | Previously it was only super-admins that could do that. | |
|
25 | - Sessions: patch baker to take expire time for redis for auto session cleanup feature. | |
|
26 | - Git: bumped git version to 2.27.0 | |
|
27 | - Packages: bumped to channelstream==0.6.14 | |
|
28 | ||
|
29 | ||
|
30 | Security | |
|
31 | ^^^^^^^^ | |
|
32 | ||
|
33 | - Issue trackers: fix XSS with description field. | |
|
34 | ||
|
35 | ||
|
36 | Performance | |
|
37 | ^^^^^^^^^^^ | |
|
38 | ||
|
39 | - Artifacts: speed-up of artifacts download request processing. | |
|
40 | ||
|
41 | ||
|
42 | Fixes | |
|
43 | ^^^^^ | |
|
44 | ||
|
45 | - Pull requests: properly save merge failure metadata. | |
|
46 | In rare cases merge check reported conflicts which there were none. | |
|
47 | - Sessions: fixed cleanup with corrupted session data issue. | |
|
48 | ||
|
49 | ||
|
50 | Upgrade notes | |
|
51 | ^^^^^^^^^^^^^ | |
|
52 | ||
|
53 | - Scheduled feature release. | |
|
54 | - Git version was bumped to 2.27.0 |
@@ -0,0 +1,55 b'' | |||
|
1 | |RCE| 4.22.0 |RNS| | |
|
2 | ------------------ | |
|
3 | ||
|
4 | Release Date | |
|
5 | ^^^^^^^^^^^^ | |
|
6 | ||
|
7 | - 2020-10-12 | |
|
8 | ||
|
9 | ||
|
10 | New Features | |
|
11 | ^^^^^^^^^^^^ | |
|
12 | ||
|
13 | - Reviewers: added observers as another role for reviewers. | |
|
14 | Observers is a role that doesn't require voting, but still gets notified about | |
|
15 | PR and should participate in review process. | |
|
16 | - Issue trackers: implemented more sophisticated ticket data extraction based on | |
|
17 | advanced regex module. This allows using ticket references without false positives | |
|
18 | like catching ticket data in an URL. | |
|
19 | - Channelstream: Notification about updates and comments now works via API, and both | |
|
20 | Pull-requests and individual commits. | |
|
21 | ||
|
22 | ||
|
23 | General | |
|
24 | ^^^^^^^ | |
|
25 | ||
|
26 | - Data tables: unified tables look for main pages of rhodecode repo pages. | |
|
27 | - Users: autocomplete now sorts by matched username to show best matches first. | |
|
28 | - Pull requests: only allow actual reviewers to leave status/votes in order to not | |
|
29 | confuse others users about voting from people who aren't actual reviewers. | |
|
30 | ||
|
31 | Security | |
|
32 | ^^^^^^^^ | |
|
33 | ||
|
34 | ||
|
35 | ||
|
36 | Performance | |
|
37 | ^^^^^^^^^^^ | |
|
38 | ||
|
39 | - Default reviewers: optimize diff data, and creation of PR with advanced default reviewers | |
|
40 | - default-reviewers: diff data should load more things lazy for better performance. | |
|
41 | - Pull requests: limit the amount of data saved in default reviewers data for better memory usage | |
|
42 | - DB: don't use lazy loaders on PR related objects, to optimize memory usage on large | |
|
43 | Pull requests with lots of comments, and commits. | |
|
44 | ||
|
45 | Fixes | |
|
46 | ^^^^^ | |
|
47 | ||
|
48 | - Quick search bar: fixes #5634, crash when search on non-ascii characters. | |
|
49 | - Sidebar: few fixes for panel rendering of reviewers/observers for both commits and PRS. | |
|
50 | ||
|
51 | Upgrade notes | |
|
52 | ^^^^^^^^^^^^^ | |
|
53 | ||
|
54 | - Scheduled feature release. | |
|
55 |
@@ -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 |
@@ -68,3 +68,5 b' 7ac623a4a2405917e2af660d645ded662011e40d' | |||
|
68 | 68 | ef7ffda65eeb90c3ba88590a6cb816ef9b0bc232 v4.19.3 |
|
69 | 69 | 3e635489bb7961df93b01e42454ad1a8730ae968 v4.20.0 |
|
70 | 70 | 7e2eb896a02ca7cd2cd9f0f853ef3dac3f0039e3 v4.20.1 |
|
71 | 8bb5fece08ab65986225b184e46f53d2a71729cb v4.21.0 | |
|
72 | 90734aac31ee4563bbe665a43ff73190cc762275 v4.22.0 |
@@ -90,7 +90,7 b' comment_pull_request' | |||
|
90 | 90 | create_pull_request |
|
91 | 91 | ------------------- |
|
92 | 92 | |
|
93 | .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, owner=<Optional:<OptionalAttr:apiuser>>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>) | |
|
93 | .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, owner=<Optional:<OptionalAttr:apiuser>>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, observers=<Optional:None>) | |
|
94 | 94 | |
|
95 | 95 | Creates a new pull request. |
|
96 | 96 | |
@@ -128,6 +128,13 b' create_pull_request' | |||
|
128 | 128 | Accepts username strings or objects of the format: |
|
129 | 129 | |
|
130 | 130 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
131 | :param observers: Set the new pull request observers list. | |
|
132 | Reviewer defined by review rules will be added automatically to the | |
|
133 | defined list. This feature is only available in RhodeCode EE | |
|
134 | :type observers: Optional(list) | |
|
135 | Accepts username strings or objects of the format: | |
|
136 | ||
|
137 | [{'username': 'nick', 'reasons': ['original author']}] | |
|
131 | 138 | |
|
132 | 139 | |
|
133 | 140 | get_pull_request |
@@ -392,7 +399,7 b' merge_pull_request' | |||
|
392 | 399 | update_pull_request |
|
393 | 400 | ------------------- |
|
394 | 401 | |
|
395 | .. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>) | |
|
402 | .. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, observers=<Optional:None>, update_commits=<Optional:None>) | |
|
396 | 403 | |
|
397 | 404 | Updates a pull request. |
|
398 | 405 | |
@@ -414,7 +421,11 b' update_pull_request' | |||
|
414 | 421 | Accepts username strings or objects of the format: |
|
415 | 422 | |
|
416 | 423 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
424 | :param observers: Update pull request observers list with new value. | |
|
425 | :type observers: Optional(list) | |
|
426 | Accepts username strings or objects of the format: | |
|
417 | 427 | |
|
428 | [{'username': 'nick', 'reasons': ['should be aware about this PR']}] | |
|
418 | 429 | :param update_commits: Trigger update of commits for this pull request |
|
419 | 430 | :type: update_commits: Optional(bool) |
|
420 | 431 | |
@@ -432,6 +443,12 b' update_pull_request' | |||
|
432 | 443 | ], |
|
433 | 444 | "removed": [] |
|
434 | 445 | }, |
|
446 | "updated_observers": { | |
|
447 | "added": [ | |
|
448 | "username" | |
|
449 | ], | |
|
450 | "removed": [] | |
|
451 | }, | |
|
435 | 452 | "updated_commits": { |
|
436 | 453 | "added": [ |
|
437 | 454 | "<sha1_hash>" |
@@ -9,6 +9,8 b' Release Notes' | |||
|
9 | 9 | .. toctree:: |
|
10 | 10 | :maxdepth: 1 |
|
11 | 11 | |
|
12 | release-notes-4.22.0.rst | |
|
13 | release-notes-4.21.0.rst | |
|
12 | 14 | release-notes-4.20.1.rst |
|
13 | 15 | release-notes-4.20.0.rst |
|
14 | 16 | release-notes-4.19.3.rst |
@@ -1816,6 +1816,17 b' self: super: {' | |||
|
1816 | 1816 | license = [ pkgs.lib.licenses.mit ]; |
|
1817 | 1817 | }; |
|
1818 | 1818 | }; |
|
1819 | "regex" = super.buildPythonPackage { | |
|
1820 | name = "regex-2020.9.27"; | |
|
1821 | doCheck = false; | |
|
1822 | src = fetchurl { | |
|
1823 | url = "https://files.pythonhosted.org/packages/93/8c/17f45cdfb39b13d4b5f909e4b4c2917abcbdef9c0036919a0399769148cf/regex-2020.9.27.tar.gz"; | |
|
1824 | sha256 = "179ngfzwbsjvn5vhyzdahvmg0f7acahkwwy9bpjy1pv08bm2mwx6"; | |
|
1825 | }; | |
|
1826 | meta = { | |
|
1827 | license = [ pkgs.lib.licenses.psfl ]; | |
|
1828 | }; | |
|
1829 | }; | |
|
1819 | 1830 | "redis" = super.buildPythonPackage { |
|
1820 | 1831 | name = "redis-3.4.1"; |
|
1821 | 1832 | doCheck = false; |
@@ -1872,7 +1883,7 b' self: super: {' | |||
|
1872 | 1883 | }; |
|
1873 | 1884 | }; |
|
1874 | 1885 | "rhodecode-enterprise-ce" = super.buildPythonPackage { |
|
1875 |
name = "rhodecode-enterprise-ce-4.2 |
|
|
1886 | name = "rhodecode-enterprise-ce-4.22.0"; | |
|
1876 | 1887 | buildInputs = [ |
|
1877 | 1888 | self."pytest" |
|
1878 | 1889 | self."py" |
@@ -1946,6 +1957,7 b' self: super: {' | |||
|
1946 | 1957 | self."tzlocal" |
|
1947 | 1958 | self."pyzmq" |
|
1948 | 1959 | self."py-gfm" |
|
1960 | self."regex" | |
|
1949 | 1961 | self."redis" |
|
1950 | 1962 | self."repoze.lru" |
|
1951 | 1963 | self."requests" |
@@ -56,6 +56,7 b' pytz==2019.3' | |||
|
56 | 56 | tzlocal==1.5.1 |
|
57 | 57 | pyzmq==14.6.0 |
|
58 | 58 | py-gfm==0.1.4 |
|
59 | regex==2020.9.27 | |
|
59 | 60 | redis==3.4.1 |
|
60 | 61 | repoze.lru==0.7 |
|
61 | 62 | requests==2.22.0 |
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}' | |||
|
48 | 48 | EXTENSIONS = {} |
|
49 | 49 | |
|
50 | 50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
51 |
__dbversion__ = 10 |
|
|
51 | __dbversion__ = 110 # defines current db version for migrations | |
|
52 | 52 | __platform__ = platform.system() |
|
53 | 53 | __license__ = 'AGPLv3, and Commercial License' |
|
54 | 54 | __author__ = 'RhodeCode GmbH' |
@@ -320,7 +320,7 b' class TestCreatePullRequestApi(object):' | |||
|
320 | 320 | id_, params = build_data( |
|
321 | 321 | self.apikey_regular, 'create_pull_request', **data) |
|
322 | 322 | response = api_call(self.app, params) |
|
323 | expected_message = 'no commits found' | |
|
323 | expected_message = 'no commits found for merge between specified references' | |
|
324 | 324 | assert_error(id_, expected_message, given=response.body) |
|
325 | 325 | |
|
326 | 326 | @pytest.mark.backends("git", "hg") |
@@ -29,6 +29,7 b' from rhodecode.api.tests.utils import (' | |||
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestGetPullRequest(object): |
|
32 | ||
|
32 | 33 | @pytest.mark.backends("git", "hg") |
|
33 | 34 | def test_api_get_pull_requests(self, pr_util): |
|
34 | 35 | pull_request = pr_util.create_pull_request() |
@@ -40,6 +41,7 b' class TestGetPullRequest(object):' | |||
|
40 | 41 | target_ref=pull_request.target_ref, |
|
41 | 42 | revisions=pull_request.revisions, |
|
42 | 43 | reviewers=(), |
|
44 | observers=(), | |
|
43 | 45 | title=pull_request.title, |
|
44 | 46 | description=pull_request.description, |
|
45 | 47 | ) |
@@ -51,6 +51,7 b' class TestUpdatePullRequest(object):' | |||
|
51 | 51 | "pull_request": response.json['result']['pull_request'], |
|
52 | 52 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
53 | 53 | "updated_reviewers": {"added": [], "removed": []}, |
|
54 | "updated_observers": {"added": [], "removed": []}, | |
|
54 | 55 | } |
|
55 | 56 | |
|
56 | 57 | response_json = response.json['result'] |
@@ -111,6 +112,7 b' class TestUpdatePullRequest(object):' | |||
|
111 | 112 | "total": total_commits, |
|
112 | 113 | "removed": []}, |
|
113 | 114 | "updated_reviewers": {"added": [], "removed": []}, |
|
115 | "updated_observers": {"added": [], "removed": []}, | |
|
114 | 116 | } |
|
115 | 117 | |
|
116 | 118 | assert_ok(id_, expected, response.body) |
@@ -132,7 +134,7 b' class TestUpdatePullRequest(object):' | |||
|
132 | 134 | removed = [a.username] |
|
133 | 135 | |
|
134 | 136 | pull_request = pr_util.create_pull_request( |
|
135 | reviewers=[(a.username, ['added via API'], False, [])]) | |
|
137 | reviewers=[(a.username, ['added via API'], False, 'reviewer', [])]) | |
|
136 | 138 | |
|
137 | 139 | id_, params = build_data( |
|
138 | 140 | self.apikey, 'update_pull_request', |
@@ -146,6 +148,7 b' class TestUpdatePullRequest(object):' | |||
|
146 | 148 | "pull_request": response.json['result']['pull_request'], |
|
147 | 149 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
148 | 150 | "updated_reviewers": {"added": added, "removed": removed}, |
|
151 | "updated_observers": {"added": [], "removed": []}, | |
|
149 | 152 | } |
|
150 | 153 | |
|
151 | 154 | assert_ok(id_, expected, response.body) |
@@ -26,12 +26,15 b' from rhodecode.api.utils import (' | |||
|
26 | 26 | has_superadmin_permission, Optional, OAttr, get_repo_or_error, |
|
27 | 27 | get_pull_request_or_error, get_commit_or_error, get_user_or_error, |
|
28 | 28 | validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions) |
|
29 | from rhodecode.lib import channelstream | |
|
29 | 30 | from rhodecode.lib.auth import (HasRepoPermissionAnyApi) |
|
30 | 31 | from rhodecode.lib.base import vcs_operation_context |
|
31 | 32 | from rhodecode.lib.utils2 import str2bool |
|
33 | from rhodecode.lib.vcs.backends.base import unicode_to_reference | |
|
32 | 34 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
33 | 35 | from rhodecode.model.comment import CommentsModel |
|
34 | from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest | |
|
36 | from rhodecode.model.db import ( | |
|
37 | Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers) | |
|
35 | 38 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
|
36 | 39 | from rhodecode.model.settings import SettingsModel |
|
37 | 40 | from rhodecode.model.validation_schema import Invalid |
@@ -502,16 +505,19 b' def comment_pull_request(' | |||
|
502 | 505 | }, |
|
503 | 506 | error : null |
|
504 | 507 | """ |
|
508 | _ = request.translate | |
|
509 | ||
|
505 | 510 | pull_request = get_pull_request_or_error(pullrequestid) |
|
506 | 511 | if Optional.extract(repoid): |
|
507 | 512 | repo = get_repo_or_error(repoid) |
|
508 | 513 | else: |
|
509 | 514 | repo = pull_request.target_repo |
|
510 | 515 | |
|
516 | db_repo_name = repo.repo_name | |
|
511 | 517 | auth_user = apiuser |
|
512 | 518 | if not isinstance(userid, Optional): |
|
513 | 519 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( |
|
514 |
user=apiuser, repo_name= |
|
|
520 | user=apiuser, repo_name=db_repo_name) | |
|
515 | 521 | if has_superadmin_permission(apiuser) or is_repo_admin: |
|
516 | 522 | apiuser = get_user_or_error(userid) |
|
517 | 523 | auth_user = apiuser.AuthUser() |
@@ -596,6 +602,7 b' def comment_pull_request(' | |||
|
596 | 602 | extra_recipients=extra_recipients, |
|
597 | 603 | send_email=send_email |
|
598 | 604 | ) |
|
605 | is_inline = comment.is_inline | |
|
599 | 606 | |
|
600 | 607 | if allowed_to_change_status and status: |
|
601 | 608 | old_calculated_status = pull_request.calculated_review_status() |
@@ -628,14 +635,39 b' def comment_pull_request(' | |||
|
628 | 635 | 'comment_id': comment.comment_id if comment else None, |
|
629 | 636 | 'status': {'given': status, 'was_changed': status_change}, |
|
630 | 637 | } |
|
638 | ||
|
639 | comment_broadcast_channel = channelstream.comment_channel( | |
|
640 | db_repo_name, pull_request_obj=pull_request) | |
|
641 | ||
|
642 | comment_data = data | |
|
643 | comment_type = 'inline' if is_inline else 'general' | |
|
644 | channelstream.comment_channelstream_push( | |
|
645 | request, comment_broadcast_channel, apiuser, | |
|
646 | _('posted a new {} comment').format(comment_type), | |
|
647 | comment_data=comment_data) | |
|
648 | ||
|
631 | 649 | return data |
|
632 | 650 | |
|
651 | def _reviewers_validation(obj_list): | |
|
652 | schema = ReviewerListSchema() | |
|
653 | try: | |
|
654 | reviewer_objects = schema.deserialize(obj_list) | |
|
655 | except Invalid as err: | |
|
656 | raise JSONRPCValidationError(colander_exc=err) | |
|
657 | ||
|
658 | # validate users | |
|
659 | for reviewer_object in reviewer_objects: | |
|
660 | user = get_user_or_error(reviewer_object['username']) | |
|
661 | reviewer_object['user_id'] = user.user_id | |
|
662 | return reviewer_objects | |
|
663 | ||
|
633 | 664 | |
|
634 | 665 | @jsonrpc_method() |
|
635 | 666 | def create_pull_request( |
|
636 | 667 | request, apiuser, source_repo, target_repo, source_ref, target_ref, |
|
637 | 668 | owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''), |
|
638 |
description_renderer=Optional(''), |
|
|
669 | description_renderer=Optional(''), | |
|
670 | reviewers=Optional(None), observers=Optional(None)): | |
|
639 | 671 | """ |
|
640 | 672 | Creates a new pull request. |
|
641 | 673 | |
@@ -673,6 +705,13 b' def create_pull_request(' | |||
|
673 | 705 | Accepts username strings or objects of the format: |
|
674 | 706 | |
|
675 | 707 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
708 | :param observers: Set the new pull request observers list. | |
|
709 | Reviewer defined by review rules will be added automatically to the | |
|
710 | defined list. This feature is only available in RhodeCode EE | |
|
711 | :type observers: Optional(list) | |
|
712 | Accepts username strings or objects of the format: | |
|
713 | ||
|
714 | [{'username': 'nick', 'reasons': ['original author']}] | |
|
676 | 715 | """ |
|
677 | 716 | |
|
678 | 717 | source_db_repo = get_repo_or_error(source_repo) |
@@ -686,34 +725,39 b' def create_pull_request(' | |||
|
686 | 725 | full_source_ref = resolve_ref_or_error(source_ref, source_db_repo) |
|
687 | 726 | full_target_ref = resolve_ref_or_error(target_ref, target_db_repo) |
|
688 | 727 | |
|
689 |
|
|
|
690 |
|
|
|
728 | get_commit_or_error(full_source_ref, source_db_repo) | |
|
729 | get_commit_or_error(full_target_ref, target_db_repo) | |
|
691 | 730 | |
|
692 | 731 | reviewer_objects = Optional.extract(reviewers) or [] |
|
732 | observer_objects = Optional.extract(observers) or [] | |
|
693 | 733 | |
|
694 | 734 | # serialize and validate passed in given reviewers |
|
695 | 735 | if reviewer_objects: |
|
696 | schema = ReviewerListSchema() | |
|
697 | try: | |
|
698 | reviewer_objects = schema.deserialize(reviewer_objects) | |
|
699 | except Invalid as err: | |
|
700 | raise JSONRPCValidationError(colander_exc=err) | |
|
736 | reviewer_objects = _reviewers_validation(reviewer_objects) | |
|
737 | ||
|
738 | if observer_objects: | |
|
739 | observer_objects = _reviewers_validation(reviewer_objects) | |
|
701 | 740 | |
|
702 | # validate users | |
|
703 | for reviewer_object in reviewer_objects: | |
|
704 | user = get_user_or_error(reviewer_object['username']) | |
|
705 | reviewer_object['user_id'] = user.user_id | |
|
741 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
|
742 | PullRequestModel().get_reviewer_functions() | |
|
706 | 743 | |
|
707 | get_default_reviewers_data, validate_default_reviewers = \ | |
|
708 | PullRequestModel().get_reviewer_functions() | |
|
744 | source_ref_obj = unicode_to_reference(full_source_ref) | |
|
745 | target_ref_obj = unicode_to_reference(full_target_ref) | |
|
709 | 746 | |
|
710 | 747 | # recalculate reviewers logic, to make sure we can validate this |
|
711 | 748 | default_reviewers_data = get_default_reviewers_data( |
|
712 |
owner, |
|
|
713 |
source_ |
|
|
749 | owner, | |
|
750 | source_db_repo, | |
|
751 | source_ref_obj, | |
|
752 | target_db_repo, | |
|
753 | target_ref_obj, | |
|
754 | ) | |
|
714 | 755 | |
|
715 | # now MERGE our given with the calculated | |
|
716 | reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects | |
|
756 | # now MERGE our given with the calculated from the default rules | |
|
757 | just_reviewers = [ | |
|
758 | x for x in default_reviewers_data['reviewers'] | |
|
759 | if x['role'] == PullRequestReviewers.ROLE_REVIEWER] | |
|
760 | reviewer_objects = just_reviewers + reviewer_objects | |
|
717 | 761 | |
|
718 | 762 | try: |
|
719 | 763 | reviewers = validate_default_reviewers( |
@@ -721,9 +765,21 b' def create_pull_request(' | |||
|
721 | 765 | except ValueError as e: |
|
722 | 766 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
723 | 767 | |
|
768 | # now MERGE our given with the calculated from the default rules | |
|
769 | just_observers = [ | |
|
770 | x for x in default_reviewers_data['reviewers'] | |
|
771 | if x['role'] == PullRequestReviewers.ROLE_OBSERVER] | |
|
772 | observer_objects = just_observers + observer_objects | |
|
773 | ||
|
774 | try: | |
|
775 | observers = validate_observers( | |
|
776 | observer_objects, default_reviewers_data) | |
|
777 | except ValueError as e: | |
|
778 | raise JSONRPCError('Observer Validation: {}'.format(e)) | |
|
779 | ||
|
724 | 780 | title = Optional.extract(title) |
|
725 | 781 | if not title: |
|
726 |
title_source_ref = source_ref. |
|
|
782 | title_source_ref = source_ref_obj.name | |
|
727 | 783 | title = PullRequestModel().generate_pullrequest_title( |
|
728 | 784 | source=source_repo, |
|
729 | 785 | source_ref=title_source_ref, |
@@ -732,20 +788,17 b' def create_pull_request(' | |||
|
732 | 788 | |
|
733 | 789 | diff_info = default_reviewers_data['diff_info'] |
|
734 | 790 | common_ancestor_id = diff_info['ancestor'] |
|
735 | commits = diff_info['commits'] | |
|
791 | # NOTE(marcink): reversed is consistent with how we open it in the WEB interface | |
|
792 | commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])] | |
|
736 | 793 | |
|
737 | 794 | if not common_ancestor_id: |
|
738 | raise JSONRPCError('no common ancestor found') | |
|
795 | raise JSONRPCError('no common ancestor found between specified references') | |
|
739 | 796 | |
|
740 | 797 | if not commits: |
|
741 | raise JSONRPCError('no commits found') | |
|
742 | ||
|
743 | # NOTE(marcink): reversed is consistent with how we open it in the WEB interface | |
|
744 | revisions = [commit.raw_id for commit in reversed(commits)] | |
|
798 | raise JSONRPCError('no commits found for merge between specified references') | |
|
745 | 799 | |
|
746 | 800 | # recalculate target ref based on ancestor |
|
747 | target_ref_type, target_ref_name, __ = full_target_ref.split(':') | |
|
748 | full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id)) | |
|
801 | full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id)) | |
|
749 | 802 | |
|
750 | 803 | # fetch renderer, if set fallback to plain in case of PR |
|
751 | 804 | rc_config = SettingsModel().get_all_settings() |
@@ -760,8 +813,9 b' def create_pull_request(' | |||
|
760 | 813 | target_repo=target_repo, |
|
761 | 814 | target_ref=full_target_ref, |
|
762 | 815 | common_ancestor_id=common_ancestor_id, |
|
763 |
revisions= |
|
|
816 | revisions=commits, | |
|
764 | 817 | reviewers=reviewers, |
|
818 | observers=observers, | |
|
765 | 819 | title=title, |
|
766 | 820 | description=description, |
|
767 | 821 | description_renderer=description_renderer, |
@@ -781,7 +835,7 b' def create_pull_request(' | |||
|
781 | 835 | def update_pull_request( |
|
782 | 836 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
783 | 837 | title=Optional(''), description=Optional(''), description_renderer=Optional(''), |
|
784 | reviewers=Optional(None), update_commits=Optional(None)): | |
|
838 | reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)): | |
|
785 | 839 | """ |
|
786 | 840 | Updates a pull request. |
|
787 | 841 | |
@@ -803,7 +857,11 b' def update_pull_request(' | |||
|
803 | 857 | Accepts username strings or objects of the format: |
|
804 | 858 | |
|
805 | 859 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
860 | :param observers: Update pull request observers list with new value. | |
|
861 | :type observers: Optional(list) | |
|
862 | Accepts username strings or objects of the format: | |
|
806 | 863 | |
|
864 | [{'username': 'nick', 'reasons': ['should be aware about this PR']}] | |
|
807 | 865 | :param update_commits: Trigger update of commits for this pull request |
|
808 | 866 | :type: update_commits: Optional(bool) |
|
809 | 867 | |
@@ -821,6 +879,12 b' def update_pull_request(' | |||
|
821 | 879 | ], |
|
822 | 880 | "removed": [] |
|
823 | 881 | }, |
|
882 | "updated_observers": { | |
|
883 | "added": [ | |
|
884 | "username" | |
|
885 | ], | |
|
886 | "removed": [] | |
|
887 | }, | |
|
824 | 888 | "updated_commits": { |
|
825 | 889 | "added": [ |
|
826 | 890 | "<sha1_hash>" |
@@ -852,36 +916,14 b' def update_pull_request(' | |||
|
852 | 916 | pullrequestid,)) |
|
853 | 917 | |
|
854 | 918 | reviewer_objects = Optional.extract(reviewers) or [] |
|
855 | ||
|
856 | if reviewer_objects: | |
|
857 | schema = ReviewerListSchema() | |
|
858 | try: | |
|
859 | reviewer_objects = schema.deserialize(reviewer_objects) | |
|
860 | except Invalid as err: | |
|
861 | raise JSONRPCValidationError(colander_exc=err) | |
|
862 | ||
|
863 | # validate users | |
|
864 | for reviewer_object in reviewer_objects: | |
|
865 | user = get_user_or_error(reviewer_object['username']) | |
|
866 | reviewer_object['user_id'] = user.user_id | |
|
867 | ||
|
868 | get_default_reviewers_data, get_validated_reviewers = \ | |
|
869 | PullRequestModel().get_reviewer_functions() | |
|
870 | ||
|
871 | # re-use stored rules | |
|
872 | reviewer_rules = pull_request.reviewer_data | |
|
873 | try: | |
|
874 | reviewers = get_validated_reviewers( | |
|
875 | reviewer_objects, reviewer_rules) | |
|
876 | except ValueError as e: | |
|
877 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) | |
|
878 | else: | |
|
879 | reviewers = [] | |
|
919 | observer_objects = Optional.extract(observers) or [] | |
|
880 | 920 | |
|
881 | 921 | title = Optional.extract(title) |
|
882 | 922 | description = Optional.extract(description) |
|
883 | 923 | description_renderer = Optional.extract(description_renderer) |
|
884 | 924 | |
|
925 | # Update title/description | |
|
926 | title_changed = False | |
|
885 | 927 | if title or description: |
|
886 | 928 | PullRequestModel().edit( |
|
887 | 929 | pull_request, |
@@ -890,8 +932,12 b' def update_pull_request(' | |||
|
890 | 932 | description_renderer or pull_request.description_renderer, |
|
891 | 933 | apiuser) |
|
892 | 934 | Session().commit() |
|
935 | title_changed = True | |
|
893 | 936 | |
|
894 | 937 | commit_changes = {"added": [], "common": [], "removed": []} |
|
938 | ||
|
939 | # Update commits | |
|
940 | commits_changed = False | |
|
895 | 941 | if str2bool(Optional.extract(update_commits)): |
|
896 | 942 | |
|
897 | 943 | if pull_request.pull_request_state != PullRequest.STATE_CREATED: |
@@ -907,12 +953,44 b' def update_pull_request(' | |||
|
907 | 953 | pull_request, db_user) |
|
908 | 954 | commit_changes = update_response.changes or commit_changes |
|
909 | 955 | Session().commit() |
|
956 | commits_changed = True | |
|
910 | 957 | |
|
958 | # Update reviewers | |
|
959 | # serialize and validate passed in given reviewers | |
|
960 | if reviewer_objects: | |
|
961 | reviewer_objects = _reviewers_validation(reviewer_objects) | |
|
962 | ||
|
963 | if observer_objects: | |
|
964 | observer_objects = _reviewers_validation(reviewer_objects) | |
|
965 | ||
|
966 | # re-use stored rules | |
|
967 | default_reviewers_data = pull_request.reviewer_data | |
|
968 | ||
|
969 | __, validate_default_reviewers, validate_observers = \ | |
|
970 | PullRequestModel().get_reviewer_functions() | |
|
971 | ||
|
972 | if reviewer_objects: | |
|
973 | try: | |
|
974 | reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data) | |
|
975 | except ValueError as e: | |
|
976 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) | |
|
977 | else: | |
|
978 | reviewers = [] | |
|
979 | ||
|
980 | if observer_objects: | |
|
981 | try: | |
|
982 | observers = validate_default_reviewers(reviewer_objects, default_reviewers_data) | |
|
983 | except ValueError as e: | |
|
984 | raise JSONRPCError('Observer Validation: {}'.format(e)) | |
|
985 | else: | |
|
986 | observers = [] | |
|
987 | ||
|
988 | reviewers_changed = False | |
|
911 | 989 | reviewers_changes = {"added": [], "removed": []} |
|
912 | 990 | if reviewers: |
|
913 | 991 | old_calculated_status = pull_request.calculated_review_status() |
|
914 | 992 | added_reviewers, removed_reviewers = \ |
|
915 | PullRequestModel().update_reviewers(pull_request, reviewers, apiuser) | |
|
993 | PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance()) | |
|
916 | 994 | |
|
917 | 995 | reviewers_changes['added'] = sorted( |
|
918 | 996 | [get_user_or_error(n).username for n in added_reviewers]) |
@@ -926,13 +1004,35 b' def update_pull_request(' | |||
|
926 | 1004 | PullRequestModel().trigger_pull_request_hook( |
|
927 | 1005 | pull_request, apiuser, 'review_status_change', |
|
928 | 1006 | data={'status': calculated_status}) |
|
1007 | reviewers_changed = True | |
|
1008 | ||
|
1009 | observers_changed = False | |
|
1010 | observers_changes = {"added": [], "removed": []} | |
|
1011 | if observers: | |
|
1012 | added_observers, removed_observers = \ | |
|
1013 | PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance()) | |
|
1014 | ||
|
1015 | observers_changes['added'] = sorted( | |
|
1016 | [get_user_or_error(n).username for n in added_observers]) | |
|
1017 | observers_changes['removed'] = sorted( | |
|
1018 | [get_user_or_error(n).username for n in removed_observers]) | |
|
1019 | Session().commit() | |
|
1020 | ||
|
1021 | reviewers_changed = True | |
|
1022 | ||
|
1023 | # push changed to channelstream | |
|
1024 | if commits_changed or reviewers_changed or observers_changed: | |
|
1025 | pr_broadcast_channel = channelstream.pr_channel(pull_request) | |
|
1026 | msg = 'Pull request was updated.' | |
|
1027 | channelstream.pr_update_channelstream_push( | |
|
1028 | request, pr_broadcast_channel, apiuser, msg) | |
|
929 | 1029 | |
|
930 | 1030 | data = { |
|
931 | 'msg': 'Updated pull request `{}`'.format( | |
|
932 | pull_request.pull_request_id), | |
|
1031 | 'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id), | |
|
933 | 1032 | 'pull_request': pull_request.get_api_data(), |
|
934 | 1033 | 'updated_commits': commit_changes, |
|
935 | 'updated_reviewers': reviewers_changes | |
|
1034 | 'updated_reviewers': reviewers_changes, | |
|
1035 | 'updated_observers': observers_changes, | |
|
936 | 1036 | } |
|
937 | 1037 | |
|
938 | 1038 | return data |
@@ -29,7 +29,7 b' from rhodecode.api.utils import (' | |||
|
29 | 29 | get_user_group_or_error, get_user_or_error, validate_repo_permissions, |
|
30 | 30 | get_perm_or_error, parse_args, get_origin, build_commit_data, |
|
31 | 31 | validate_set_owner_permissions) |
|
32 | from rhodecode.lib import audit_logger, rc_cache | |
|
32 | from rhodecode.lib import audit_logger, rc_cache, channelstream | |
|
33 | 33 | from rhodecode.lib import repo_maintenance |
|
34 | 34 | from rhodecode.lib.auth import ( |
|
35 | 35 | HasPermissionAnyApi, HasUserGroupPermissionAnyApi, |
@@ -1597,10 +1597,13 b' def comment_commit(' | |||
|
1597 | 1597 | } |
|
1598 | 1598 | |
|
1599 | 1599 | """ |
|
1600 | _ = request.translate | |
|
1601 | ||
|
1600 | 1602 | repo = get_repo_or_error(repoid) |
|
1601 | 1603 | if not has_superadmin_permission(apiuser): |
|
1602 | 1604 | _perms = ('repository.read', 'repository.write', 'repository.admin') |
|
1603 | 1605 | validate_repo_permissions(apiuser, repoid, repo, _perms) |
|
1606 | db_repo_name = repo.repo_name | |
|
1604 | 1607 | |
|
1605 | 1608 | try: |
|
1606 | 1609 | commit = repo.scm_instance().get_commit(commit_id=commit_id) |
@@ -1650,6 +1653,8 b' def comment_commit(' | |||
|
1650 | 1653 | extra_recipients=extra_recipients, |
|
1651 | 1654 | send_email=send_email |
|
1652 | 1655 | ) |
|
1656 | is_inline = comment.is_inline | |
|
1657 | ||
|
1653 | 1658 | if status: |
|
1654 | 1659 | # also do a status change |
|
1655 | 1660 | try: |
@@ -1669,6 +1674,17 b' def comment_commit(' | |||
|
1669 | 1674 | data={'comment': comment, 'commit': commit}) |
|
1670 | 1675 | |
|
1671 | 1676 | Session().commit() |
|
1677 | ||
|
1678 | comment_broadcast_channel = channelstream.comment_channel( | |
|
1679 | db_repo_name, commit_obj=commit) | |
|
1680 | ||
|
1681 | comment_data = {'comment': comment, 'comment_id': comment.comment_id} | |
|
1682 | comment_type = 'inline' if is_inline else 'general' | |
|
1683 | channelstream.comment_channelstream_push( | |
|
1684 | request, comment_broadcast_channel, apiuser, | |
|
1685 | _('posted a new {} comment').format(comment_type), | |
|
1686 | comment_data=comment_data) | |
|
1687 | ||
|
1672 | 1688 | return { |
|
1673 | 1689 | 'msg': ( |
|
1674 | 1690 | 'Commented on commit `%s` for repository `%s`' % ( |
@@ -474,9 +474,18 b' class AdminSettingsView(BaseAppView):' | |||
|
474 | 474 | route_name='admin_settings_issuetracker_test', request_method='POST', |
|
475 | 475 | renderer='string', xhr=True) |
|
476 | 476 | def settings_issuetracker_test(self): |
|
477 | return h.urlify_commit_message( | |
|
477 | error_container = [] | |
|
478 | ||
|
479 | urlified_commit = h.urlify_commit_message( | |
|
478 | 480 | self.request.POST.get('test_text', ''), |
|
479 | 'repo_group/test_repo1') | |
|
481 | 'repo_group/test_repo1', error_container=error_container) | |
|
482 | if error_container: | |
|
483 | def converter(inp): | |
|
484 | return h.html_escape(unicode(inp)) | |
|
485 | ||
|
486 | return 'ERRORS: ' + '\n'.join(map(converter, error_container)) | |
|
487 | ||
|
488 | return urlified_commit | |
|
480 | 489 | |
|
481 | 490 | @LoginRequired() |
|
482 | 491 | @HasPermissionAllDecorator('hg.admin') |
@@ -34,6 +34,7 b' log = logging.getLogger(__name__)' | |||
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | class DebugStyleView(BaseAppView): |
|
37 | ||
|
37 | 38 | def load_default_context(self): |
|
38 | 39 | c = self._get_local_tmpl_context() |
|
39 | 40 | |
@@ -75,6 +76,7 b' Check if we should use full-topic or min' | |||
|
75 | 76 | source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'), |
|
76 | 77 | target_ref_parts=AttributeDict(type='branch', name='master'), |
|
77 | 78 | ) |
|
79 | ||
|
78 | 80 | target_repo = AttributeDict(repo_name='repo_group/target_repo') |
|
79 | 81 | source_repo = AttributeDict(repo_name='repo_group/source_repo') |
|
80 | 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 | 85 | 'added': ['aaaaaaabbbbb', 'cccccccddddddd'], |
|
84 | 86 | 'removed': ['eeeeeeeeeee'], |
|
85 | 87 | }) |
|
88 | ||
|
86 | 89 | file_changes = AttributeDict({ |
|
87 | 90 | 'added': ['a/file1.md', 'file2.py'], |
|
88 | 91 | 'modified': ['b/modified_file.rst'], |
@@ -97,15 +100,19 b' Check if we should use full-topic or min' | |||
|
97 | 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 | 101 | 'exc_type': 'AttributeError' |
|
99 | 102 | } |
|
103 | ||
|
100 | 104 | email_kwargs = { |
|
101 | 105 | 'test': {}, |
|
106 | ||
|
102 | 107 | 'message': { |
|
103 | 108 | 'body': 'message body !' |
|
104 | 109 | }, |
|
110 | ||
|
105 | 111 | 'email_test': { |
|
106 | 112 | 'user': user, |
|
107 | 113 | 'date': datetime.datetime.now(), |
|
108 | 114 | }, |
|
115 | ||
|
109 | 116 | 'exception': { |
|
110 | 117 | 'email_prefix': '[RHODECODE ERROR]', |
|
111 | 118 | 'exc_id': exc_traceback['exc_id'], |
@@ -113,6 +120,7 b' Check if we should use full-topic or min' | |||
|
113 | 120 | 'exc_type_name': 'NameError', |
|
114 | 121 | 'exc_traceback': exc_traceback, |
|
115 | 122 | }, |
|
123 | ||
|
116 | 124 | 'password_reset': { |
|
117 | 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 | 129 | 'email': 'test@rhodecode.com', |
|
122 | 130 | 'first_admin_email': User.get_first_super_admin().email |
|
123 | 131 | }, |
|
132 | ||
|
124 | 133 | 'password_reset_confirmation': { |
|
125 | 134 | 'new_password': 'new-password-example', |
|
126 | 135 | 'user': user, |
@@ -128,6 +137,7 b' Check if we should use full-topic or min' | |||
|
128 | 137 | 'email': 'test@rhodecode.com', |
|
129 | 138 | 'first_admin_email': User.get_first_super_admin().email |
|
130 | 139 | }, |
|
140 | ||
|
131 | 141 | 'registration': { |
|
132 | 142 | 'user': user, |
|
133 | 143 | 'date': datetime.datetime.now(), |
@@ -161,6 +171,7 b' Check if we should use full-topic or min' | |||
|
161 | 171 | 'mention': True, |
|
162 | 172 | |
|
163 | 173 | }, |
|
174 | ||
|
164 | 175 | 'pull_request_comment+status': { |
|
165 | 176 | 'user': user, |
|
166 | 177 | |
@@ -201,6 +212,7 b' def db():' | |||
|
201 | 212 | 'mention': True, |
|
202 | 213 | |
|
203 | 214 | }, |
|
215 | ||
|
204 | 216 | 'pull_request_comment+file': { |
|
205 | 217 | 'user': user, |
|
206 | 218 | |
@@ -303,6 +315,7 b' This should work better !' | |||
|
303 | 315 | 'renderer_type': 'markdown', |
|
304 | 316 | 'mention': True, |
|
305 | 317 | }, |
|
318 | ||
|
306 | 319 | 'cs_comment+status': { |
|
307 | 320 | 'user': user, |
|
308 | 321 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
@@ -328,6 +341,7 b' This is a multiline comment :)' | |||
|
328 | 341 | 'renderer_type': 'markdown', |
|
329 | 342 | 'mention': True, |
|
330 | 343 | }, |
|
344 | ||
|
331 | 345 | 'cs_comment+file': { |
|
332 | 346 | 'user': user, |
|
333 | 347 | 'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'), |
@@ -371,8 +385,58 b' users: description edit fixes' | |||
|
371 | 385 | 'pull_request_source_repo_url': 'http://source-repo/url', |
|
372 | 386 | |
|
373 | 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 | 442 | template_type = email_id.split('+')[0] |
@@ -401,6 +465,7 b' users: description edit fixes' | |||
|
401 | 465 | c = self.load_default_context() |
|
402 | 466 | c.active = os.path.splitext(t_path)[0] |
|
403 | 467 | c.came_from = '' |
|
468 | # NOTE(marcink): extend the email types with variations based on data sets | |
|
404 | 469 | c.email_types = { |
|
405 | 470 | 'cs_comment+file': {}, |
|
406 | 471 | 'cs_comment+status': {}, |
@@ -409,6 +474,9 b' users: description edit fixes' | |||
|
409 | 474 | 'pull_request_comment+status': {}, |
|
410 | 475 | |
|
411 | 476 | 'pull_request_update': {}, |
|
477 | ||
|
478 | 'pull_request+reviewer_role': {}, | |
|
479 | 'pull_request+observer_role': {}, | |
|
412 | 480 | } |
|
413 | 481 | c.email_types.update(EmailNotificationModel.email_types) |
|
414 | 482 |
@@ -32,7 +32,7 b' from rhodecode.lib.auth import (' | |||
|
32 | 32 | HasRepoGroupPermissionAny, AuthUser) |
|
33 | 33 | from rhodecode.lib.codeblocks import filenode_as_lines_tokens |
|
34 | 34 | from rhodecode.lib.index import searcher_from_config |
|
35 | from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int | |
|
35 | from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int, safe_str | |
|
36 | 36 | from rhodecode.lib.vcs.nodes import FileNode |
|
37 | 37 | from rhodecode.model.db import ( |
|
38 | 38 | func, true, or_, case, cast, in_filter_generator, String, Session, |
@@ -331,7 +331,8 b' class HomeView(BaseAppView, DataGridAppV' | |||
|
331 | 331 | { |
|
332 | 332 | 'id': obj.pull_request_id, |
|
333 | 333 | 'value': org_query, |
|
334 |
'value_display': 'pull request: `!{} - {}`'.format( |
|
|
334 | 'value_display': 'pull request: `!{} - {}`'.format( | |
|
335 | obj.pull_request_id, safe_str(obj.title[:50])), | |
|
335 | 336 | 'type': 'pull_request', |
|
336 | 337 | 'url': h.route_path('pull_requests_global', pull_request_id=obj.pull_request_id) |
|
337 | 338 | } |
@@ -734,8 +734,8 b' class MyAccountView(BaseAppView, DataGri' | |||
|
734 | 734 | comments_model = CommentsModel() |
|
735 | 735 | for pr in pull_requests: |
|
736 | 736 | repo_id = pr.target_repo_id |
|
737 | comments = comments_model.get_all_comments( | |
|
738 | repo_id, pull_request=pr) | |
|
737 | comments_count = comments_model.get_all_comments( | |
|
738 | repo_id, pull_request=pr, count_only=True) | |
|
739 | 739 | owned = pr.user_id == self._rhodecode_user.user_id |
|
740 | 740 | |
|
741 | 741 | data.append({ |
@@ -760,8 +760,8 b' class MyAccountView(BaseAppView, DataGri' | |||
|
760 | 760 | 'author': _render('pullrequest_author', |
|
761 | 761 | pr.author.full_contact, ), |
|
762 | 762 | 'author_raw': pr.author.full_name, |
|
763 |
'comments': _render('pullrequest_comments', |
|
|
764 |
'comments_raw': |
|
|
763 | 'comments': _render('pullrequest_comments', comments_count), | |
|
764 | 'comments_raw': comments_count, | |
|
765 | 765 | 'closed': pr.is_closed(), |
|
766 | 766 | 'owned': owned |
|
767 | 767 | }) |
@@ -523,7 +523,9 b' class TestPullrequestsView(object):' | |||
|
523 | 523 | pull_request = pr_util.create_pull_request() |
|
524 | 524 | pull_request_id = pull_request.pull_request_id |
|
525 | 525 | PullRequestModel().update_reviewers( |
|
526 | pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])], | |
|
526 | pull_request_id, [ | |
|
527 | (1, ['reason'], False, 'reviewer', []), | |
|
528 | (2, ['reason2'], False, 'reviewer', [])], | |
|
527 | 529 | pull_request.author) |
|
528 | 530 | author = pull_request.user_id |
|
529 | 531 | repo = pull_request.target_repo.repo_id |
@@ -906,12 +908,13 b' class TestPullrequestsView(object):' | |||
|
906 | 908 | |
|
907 | 909 | # Change reviewers and check that a notification was made |
|
908 | 910 | PullRequestModel().update_reviewers( |
|
909 |
pull_request.pull_request_id, [ |
|
|
911 | pull_request.pull_request_id, [ | |
|
912 | (1, [], False, 'reviewer', []) | |
|
913 | ], | |
|
910 | 914 | pull_request.author) |
|
911 | 915 | assert len(notifications.all()) == 2 |
|
912 | 916 | |
|
913 | def test_create_pull_request_stores_ancestor_commit_id(self, backend, | |
|
914 | csrf_token): | |
|
917 | def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token): | |
|
915 | 918 | commits = [ |
|
916 | 919 | {'message': 'ancestor', |
|
917 | 920 | 'added': [FileNode('file_A', content='content_of_ancestor')]}, |
@@ -18,14 +18,16 b'' | |||
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | from rhodecode.lib import helpers as h | |
|
21 | from rhodecode.lib import helpers as h, rc_cache | |
|
22 | 22 | from rhodecode.lib.utils2 import safe_int |
|
23 | 23 | from rhodecode.model.pull_request import get_diff_info |
|
24 | ||
|
25 | REVIEWER_API_VERSION = 'V3' | |
|
24 | from rhodecode.model.db import PullRequestReviewers | |
|
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 | 32 | Returns json struct of a reviewer for frontend |
|
31 | 33 | |
@@ -33,11 +35,15 b' def reviewer_as_json(user, reasons=None,' | |||
|
33 | 35 | :param reasons: list of strings of why they are reviewers |
|
34 | 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 | 42 | return { |
|
38 | 43 | 'user_id': user.user_id, |
|
39 | 44 | 'reasons': reasons or [], |
|
40 | 45 | 'rules': rules or [], |
|
46 | 'role': role, | |
|
41 | 47 | 'mandatory': mandatory, |
|
42 | 48 | 'user_group': user_group, |
|
43 | 49 | 'username': user.username, |
@@ -48,21 +54,36 b' def reviewer_as_json(user, reasons=None,' | |||
|
48 | 54 | } |
|
49 | 55 | |
|
50 | 56 | |
|
51 |
def |
|
|
52 | current_user, source_repo, source_commit, target_repo, target_commit): | |
|
57 | def to_reviewers(e): | |
|
58 | if isinstance(e, (tuple, list)): | |
|
59 | return map(reviewer_as_json, e) | |
|
60 | else: | |
|
61 | return reviewer_as_json(e) | |
|
62 | ||
|
63 | ||
|
64 | def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref, | |
|
65 | include_diff_info=True): | |
|
53 | 66 | """ |
|
54 | 67 | Return json for default reviewers of a repository |
|
55 | 68 | """ |
|
56 | 69 | |
|
70 | diff_info = {} | |
|
71 | if include_diff_info: | |
|
57 | 72 | diff_info = get_diff_info( |
|
58 |
source_repo, source_ |
|
|
73 | source_repo, source_ref.commit_id, target_repo, target_ref.commit_id) | |
|
59 | 74 | |
|
60 | 75 | reasons = ['Default reviewer', 'Repository owner'] |
|
61 | 76 | json_reviewers = [reviewer_as_json( |
|
62 | user=target_repo.user, reasons=reasons, mandatory=False, rules=None)] | |
|
77 | user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)] | |
|
78 | ||
|
79 | compute_key = rc_cache.utils.compute_key_from_params( | |
|
80 | current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name, | |
|
81 | source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name, | |
|
82 | target_ref.commit_id) | |
|
63 | 83 | |
|
64 | 84 | return { |
|
65 | 85 | 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade |
|
86 | 'compute_key': compute_key, | |
|
66 | 87 | 'diff_info': diff_info, |
|
67 | 88 | 'reviewers': json_reviewers, |
|
68 | 89 | 'rules': {}, |
@@ -73,15 +94,18 b' def get_default_reviewers_data(' | |||
|
73 | 94 | def validate_default_reviewers(review_members, reviewer_rules): |
|
74 | 95 | """ |
|
75 | 96 | Function to validate submitted reviewers against the saved rules |
|
76 | ||
|
77 | 97 | """ |
|
78 | 98 | reviewers = [] |
|
79 | 99 | reviewer_by_id = {} |
|
80 | 100 | for r in review_members: |
|
81 | 101 | reviewer_user_id = safe_int(r['user_id']) |
|
82 | entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['rules']) | |
|
102 | entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules']) | |
|
83 | 103 | |
|
84 | 104 | reviewer_by_id[reviewer_user_id] = entry |
|
85 | 105 | reviewers.append(entry) |
|
86 | 106 | |
|
87 | 107 | return reviewers |
|
108 | ||
|
109 | ||
|
110 | def validate_observers(observer_members, reviewer_rules): | |
|
111 | return {} |
@@ -31,7 +31,7 b' from rhodecode.apps._base import RepoApp' | |||
|
31 | 31 | from rhodecode.apps.file_store import utils as store_utils |
|
32 | 32 | from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException |
|
33 | 33 | |
|
34 | from rhodecode.lib import diffs, codeblocks | |
|
34 | from rhodecode.lib import diffs, codeblocks, channelstream | |
|
35 | 35 | from rhodecode.lib.auth import ( |
|
36 | 36 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) |
|
37 | 37 | from rhodecode.lib.ext_json import json |
@@ -170,7 +170,9 b' class RepoCommitsView(RepoAppView):' | |||
|
170 | 170 | ) |
|
171 | 171 | reviewers_duplicates.add(_user_id) |
|
172 | 172 | |
|
173 |
c. |
|
|
173 | c.reviewers_count = len(reviewers) | |
|
174 | c.observers_count = 0 | |
|
175 | ||
|
174 | 176 | # from associated statuses, check the pull requests, and |
|
175 | 177 | # show comments from them |
|
176 | 178 | for pr in prs: |
@@ -193,7 +195,7 b' class RepoCommitsView(RepoAppView):' | |||
|
193 | 195 | |
|
194 | 196 | for review_obj, member, reasons, mandatory, status in review_statuses: |
|
195 | 197 | member_reviewer = h.reviewer_as_json( |
|
196 | member, reasons=reasons, mandatory=mandatory, | |
|
198 | member, reasons=reasons, mandatory=mandatory, role=None, | |
|
197 | 199 | user_group=None |
|
198 | 200 | ) |
|
199 | 201 | |
@@ -207,10 +209,7 b' class RepoCommitsView(RepoAppView):' | |||
|
207 | 209 | |
|
208 | 210 | # NOTE(marcink): this uses the same voting logic as in pull-requests |
|
209 | 211 | c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses) |
|
210 |
c.commit_broadcast_channel = |
|
|
211 | c.repo_name, | |
|
212 | commit.raw_id | |
|
213 | ) | |
|
212 | c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit) | |
|
214 | 213 | |
|
215 | 214 | diff = None |
|
216 | 215 | # Iterate over ranges (default commit view is always one commit) |
@@ -414,6 +413,7 b' class RepoCommitsView(RepoAppView):' | |||
|
414 | 413 | resolves_comment_id=resolves_comment_id, |
|
415 | 414 | auth_user=self._rhodecode_user |
|
416 | 415 | ) |
|
416 | is_inline = comment.is_inline | |
|
417 | 417 | |
|
418 | 418 | # get status if set ! |
|
419 | 419 | if status: |
@@ -461,6 +461,16 b' class RepoCommitsView(RepoAppView):' | |||
|
461 | 461 | data.update(comment.get_dict()) |
|
462 | 462 | data.update({'rendered_text': rendered_comment}) |
|
463 | 463 | |
|
464 | comment_broadcast_channel = channelstream.comment_channel( | |
|
465 | self.db_repo_name, commit_obj=commit) | |
|
466 | ||
|
467 | comment_data = data | |
|
468 | comment_type = 'inline' if is_inline else 'general' | |
|
469 | channelstream.comment_channelstream_push( | |
|
470 | self.request, comment_broadcast_channel, self._rhodecode_user, | |
|
471 | _('posted a new {} comment').format(comment_type), | |
|
472 | comment_data=comment_data) | |
|
473 | ||
|
464 | 474 | return data |
|
465 | 475 | |
|
466 | 476 | @LoginRequired() |
@@ -39,14 +39,16 b' from rhodecode.lib.ext_json import json' | |||
|
39 | 39 | from rhodecode.lib.auth import ( |
|
40 | 40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, |
|
41 | 41 | NotAnonymous, CSRFRequired) |
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int | |
|
43 |
from rhodecode.lib.vcs.backends.base import |
|
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist | |
|
43 | from rhodecode.lib.vcs.backends.base import ( | |
|
44 | EmptyCommit, UpdateFailureReason, unicode_to_reference) | |
|
44 | 45 | from rhodecode.lib.vcs.exceptions import ( |
|
45 | 46 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) |
|
46 | 47 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
47 | 48 | from rhodecode.model.comment import CommentsModel |
|
48 | 49 | from rhodecode.model.db import ( |
|
49 |
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository |
|
|
50 | func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository, | |
|
51 | PullRequestReviewers) | |
|
50 | 52 | from rhodecode.model.forms import PullRequestForm |
|
51 | 53 | from rhodecode.model.meta import Session |
|
52 | 54 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
@@ -104,13 +106,14 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
104 | 106 | data = [] |
|
105 | 107 | comments_model = CommentsModel() |
|
106 | 108 | for pr in pull_requests: |
|
107 | comments = comments_model.get_all_comments( | |
|
108 | self.db_repo.repo_id, pull_request=pr) | |
|
109 | comments_count = comments_model.get_all_comments( | |
|
110 | self.db_repo.repo_id, pull_request=pr, count_only=True) | |
|
109 | 111 | |
|
110 | 112 | data.append({ |
|
111 | 113 | 'name': _render('pullrequest_name', |
|
112 | 114 | pr.pull_request_id, pr.pull_request_state, |
|
113 |
pr.work_in_progress, pr.target_repo.repo_name |
|
|
115 | pr.work_in_progress, pr.target_repo.repo_name, | |
|
116 | short=True), | |
|
114 | 117 | 'name_raw': pr.pull_request_id, |
|
115 | 118 | 'status': _render('pullrequest_status', |
|
116 | 119 | pr.calculated_review_status()), |
@@ -126,8 +129,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
126 | 129 | 'author': _render('pullrequest_author', |
|
127 | 130 | pr.author.full_contact, ), |
|
128 | 131 | 'author_raw': pr.author.full_name, |
|
129 |
'comments': _render('pullrequest_comments', |
|
|
130 |
'comments_raw': |
|
|
132 | 'comments': _render('pullrequest_comments', comments_count), | |
|
133 | 'comments_raw': comments_count, | |
|
131 | 134 | 'closed': pr.is_closed(), |
|
132 | 135 | }) |
|
133 | 136 | |
@@ -310,8 +313,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
310 | 313 | pull_request_id = pull_request.pull_request_id |
|
311 | 314 | |
|
312 | 315 | c.state_progressing = pull_request.is_state_changing() |
|
313 |
c.pr_broadcast_channel = |
|
|
314 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |
|
316 | c.pr_broadcast_channel = channelstream.pr_channel(pull_request) | |
|
315 | 317 | |
|
316 | 318 | _new_state = { |
|
317 | 319 | 'created': PullRequest.STATE_CREATED, |
@@ -454,15 +456,18 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
454 | 456 | 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako' |
|
455 | 457 | return self._get_template_context(c) |
|
456 | 458 | |
|
457 |
c. |
|
|
459 | c.reviewers_count = pull_request.reviewers_count | |
|
460 | c.observers_count = pull_request.observers_count | |
|
458 | 461 | |
|
459 | 462 | # reviewers and statuses |
|
460 | 463 | c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data) |
|
461 | 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 | 467 | for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses(): |
|
464 | 468 | member_reviewer = h.reviewer_as_json( |
|
465 | 469 | member, reasons=reasons, mandatory=mandatory, |
|
470 | role=review_obj.role, | |
|
466 | 471 | user_group=review_obj.rule_user_group_data() |
|
467 | 472 | ) |
|
468 | 473 | |
@@ -474,6 +479,17 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
474 | 479 | |
|
475 | 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 | 493 | general_comments, inline_comments = \ |
|
478 | 494 | self.register_comments_vars(c, pull_request_latest, versions) |
|
479 | 495 | |
@@ -745,7 +761,9 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
745 | 761 | |
|
746 | 762 | # current user review statuses for each version |
|
747 | 763 | c.review_versions = {} |
|
748 | if self._rhodecode_user.user_id in c.allowed_reviewers: | |
|
764 | is_reviewer = PullRequestModel().is_user_reviewer( | |
|
765 | pull_request, self._rhodecode_user) | |
|
766 | if is_reviewer: | |
|
749 | 767 | for co in general_comments: |
|
750 | 768 | if co.author.user_id == self._rhodecode_user.user_id: |
|
751 | 769 | status = co.status_change |
@@ -961,13 +979,16 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
961 | 979 | } |
|
962 | 980 | return data |
|
963 | 981 | |
|
982 | def _get_existing_ids(self, post_data): | |
|
983 | return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ','))) | |
|
984 | ||
|
964 | 985 | @LoginRequired() |
|
965 | 986 | @NotAnonymous() |
|
966 | 987 | @HasRepoPermissionAnyDecorator( |
|
967 | 988 | 'repository.read', 'repository.write', 'repository.admin') |
|
968 | 989 | @view_config( |
|
969 | 990 | route_name='pullrequest_comments', request_method='POST', |
|
970 | renderer='string', xhr=True) | |
|
991 | renderer='string_html', xhr=True) | |
|
971 | 992 | def pullrequest_comments(self): |
|
972 | 993 | self.load_default_context() |
|
973 | 994 | |
@@ -997,8 +1018,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
997 | 1018 | self.register_comments_vars(c, pull_request_latest, versions) |
|
998 | 1019 | all_comments = c.inline_comments_flat + c.comments |
|
999 | 1020 | |
|
1000 | existing_ids = filter( | |
|
1001 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |
|
1021 | existing_ids = self._get_existing_ids(self.request.POST) | |
|
1002 | 1022 | return _render('comments_table', all_comments, len(all_comments), |
|
1003 | 1023 | existing_ids=existing_ids) |
|
1004 | 1024 | |
@@ -1008,7 +1028,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1008 | 1028 | 'repository.read', 'repository.write', 'repository.admin') |
|
1009 | 1029 | @view_config( |
|
1010 | 1030 | route_name='pullrequest_todos', request_method='POST', |
|
1011 | renderer='string', xhr=True) | |
|
1031 | renderer='string_html', xhr=True) | |
|
1012 | 1032 | def pullrequest_todos(self): |
|
1013 | 1033 | self.load_default_context() |
|
1014 | 1034 | |
@@ -1040,8 +1060,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1040 | 1060 | .get_pull_request_resolved_todos(pull_request) |
|
1041 | 1061 | |
|
1042 | 1062 | all_comments = c.unresolved_comments + c.resolved_comments |
|
1043 | existing_ids = filter( | |
|
1044 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |
|
1063 | existing_ids = self._get_existing_ids(self.request.POST) | |
|
1045 | 1064 | return _render('comments_table', all_comments, len(c.unresolved_comments), |
|
1046 | 1065 | todo_comments=True, existing_ids=existing_ids) |
|
1047 | 1066 | |
@@ -1128,30 +1147,35 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1128 | 1147 | source_scm = source_db_repo.scm_instance() |
|
1129 | 1148 | target_scm = target_db_repo.scm_instance() |
|
1130 | 1149 | |
|
1131 | source_commit = source_scm.get_commit(source_ref.split(':')[-1]) | |
|
1132 | target_commit = target_scm.get_commit(target_ref.split(':')[-1]) | |
|
1150 | source_ref_obj = unicode_to_reference(source_ref) | |
|
1151 | target_ref_obj = unicode_to_reference(target_ref) | |
|
1152 | ||
|
1153 | source_commit = source_scm.get_commit(source_ref_obj.commit_id) | |
|
1154 | target_commit = target_scm.get_commit(target_ref_obj.commit_id) | |
|
1133 | 1155 | |
|
1134 | 1156 | ancestor = source_scm.get_common_ancestor( |
|
1135 | 1157 | source_commit.raw_id, target_commit.raw_id, target_scm) |
|
1136 | 1158 | |
|
1137 | 1159 | # recalculate target ref based on ancestor |
|
1138 | target_ref_type, target_ref_name, __ = _form['target_ref'].split(':') | |
|
1139 | target_ref = ':'.join((target_ref_type, target_ref_name, ancestor)) | |
|
1160 | target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor)) | |
|
1140 | 1161 | |
|
1141 | get_default_reviewers_data, validate_default_reviewers = \ | |
|
1162 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
|
1142 | 1163 | PullRequestModel().get_reviewer_functions() |
|
1143 | 1164 | |
|
1144 | 1165 | # recalculate reviewers logic, to make sure we can validate this |
|
1145 | 1166 | reviewer_rules = get_default_reviewers_data( |
|
1146 |
self._rhodecode_db_user, |
|
|
1147 |
source_ |
|
|
1167 | self._rhodecode_db_user, | |
|
1168 | source_db_repo, | |
|
1169 | source_ref_obj, | |
|
1170 | target_db_repo, | |
|
1171 | target_ref_obj, | |
|
1172 | include_diff_info=False) | |
|
1148 | 1173 | |
|
1149 |
|
|
|
1150 | reviewers = validate_default_reviewers( | |
|
1151 | given_reviewers, reviewer_rules) | |
|
1174 | reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules) | |
|
1175 | observers = validate_observers(_form['observer_members'], reviewer_rules) | |
|
1152 | 1176 | |
|
1153 | 1177 | pullrequest_title = _form['pullrequest_title'] |
|
1154 |
title_source_ref = source_ref. |
|
|
1178 | title_source_ref = source_ref_obj.name | |
|
1155 | 1179 | if not pullrequest_title: |
|
1156 | 1180 | pullrequest_title = PullRequestModel().generate_pullrequest_title( |
|
1157 | 1181 | source=source_repo, |
@@ -1172,6 +1196,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1172 | 1196 | revisions=commit_ids, |
|
1173 | 1197 | common_ancestor_id=common_ancestor_id, |
|
1174 | 1198 | reviewers=reviewers, |
|
1199 | observers=observers, | |
|
1175 | 1200 | title=pullrequest_title, |
|
1176 | 1201 | description=description, |
|
1177 | 1202 | description_renderer=description_renderer, |
@@ -1221,20 +1246,28 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1221 | 1246 | 'redirect_url': redirect_url} |
|
1222 | 1247 | |
|
1223 | 1248 | is_state_changing = pull_request.is_state_changing() |
|
1224 |
c.pr_broadcast_channel = |
|
|
1225 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |
|
1249 | c.pr_broadcast_channel = channelstream.pr_channel(pull_request) | |
|
1226 | 1250 | |
|
1227 | 1251 | # only owner or admin can update it |
|
1228 | 1252 | allowed_to_update = PullRequestModel().check_user_update( |
|
1229 | 1253 | pull_request, self._rhodecode_user) |
|
1254 | ||
|
1230 | 1255 | if allowed_to_update: |
|
1231 | 1256 | controls = peppercorn.parse(self.request.POST.items()) |
|
1232 | 1257 | force_refresh = str2bool(self.request.POST.get('force_refresh')) |
|
1233 | 1258 | |
|
1234 | 1259 | if 'review_members' in controls: |
|
1235 | 1260 | self._update_reviewers( |
|
1261 | c, | |
|
1236 | 1262 | pull_request, controls['review_members'], |
|
1237 |
pull_request.reviewer_data |
|
|
1263 | pull_request.reviewer_data, | |
|
1264 | PullRequestReviewers.ROLE_REVIEWER) | |
|
1265 | elif 'observer_members' in controls: | |
|
1266 | self._update_reviewers( | |
|
1267 | c, | |
|
1268 | pull_request, controls['observer_members'], | |
|
1269 | pull_request.reviewer_data, | |
|
1270 | PullRequestReviewers.ROLE_OBSERVER) | |
|
1238 | 1271 | elif str2bool(self.request.POST.get('update_commits', 'false')): |
|
1239 | 1272 | if is_state_changing: |
|
1240 | 1273 | log.debug('commits update: forbidden because pull request is in state %s', |
@@ -1255,6 +1288,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1255 | 1288 | elif str2bool(self.request.POST.get('edit_pull_request', 'false')): |
|
1256 | 1289 | self._edit_pull_request(pull_request) |
|
1257 | 1290 | else: |
|
1291 | log.error('Unhandled update data.') | |
|
1258 | 1292 | raise HTTPBadRequest() |
|
1259 | 1293 | |
|
1260 | 1294 | return {'response': True, |
@@ -1262,6 +1296,9 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1262 | 1296 | raise HTTPForbidden() |
|
1263 | 1297 | |
|
1264 | 1298 | def _edit_pull_request(self, pull_request): |
|
1299 | """ | |
|
1300 | Edit title and description | |
|
1301 | """ | |
|
1265 | 1302 | _ = self.request.translate |
|
1266 | 1303 | |
|
1267 | 1304 | try: |
@@ -1302,27 +1339,15 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1302 | 1339 | |
|
1303 | 1340 | msg = _(u'Pull request updated to "{source_commit_id}" with ' |
|
1304 | 1341 | u'{count_added} added, {count_removed} removed commits. ' |
|
1305 | u'Source of changes: {change_source}') | |
|
1342 | u'Source of changes: {change_source}.') | |
|
1306 | 1343 | msg = msg.format( |
|
1307 | 1344 | source_commit_id=pull_request.source_ref_parts.commit_id, |
|
1308 | 1345 | count_added=len(resp.changes.added), |
|
1309 | 1346 | count_removed=len(resp.changes.removed), |
|
1310 | 1347 | change_source=changed) |
|
1311 | 1348 | h.flash(msg, category='success') |
|
1312 | ||
|
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) | |
|
1349 | channelstream.pr_update_channelstream_push( | |
|
1350 | self.request, c.pr_broadcast_channel, self._rhodecode_user, msg) | |
|
1326 | 1351 | else: |
|
1327 | 1352 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] |
|
1328 | 1353 | warning_reasons = [ |
@@ -1332,6 +1357,55 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1332 | 1357 | category = 'warning' if resp.reason in warning_reasons else 'error' |
|
1333 | 1358 | h.flash(msg, category=category) |
|
1334 | 1359 | |
|
1360 | def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role): | |
|
1361 | _ = self.request.translate | |
|
1362 | ||
|
1363 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ | |
|
1364 | PullRequestModel().get_reviewer_functions() | |
|
1365 | ||
|
1366 | if role == PullRequestReviewers.ROLE_REVIEWER: | |
|
1367 | try: | |
|
1368 | reviewers = validate_default_reviewers(review_members, reviewer_rules) | |
|
1369 | except ValueError as e: | |
|
1370 | log.error('Reviewers Validation: {}'.format(e)) | |
|
1371 | h.flash(e, category='error') | |
|
1372 | return | |
|
1373 | ||
|
1374 | old_calculated_status = pull_request.calculated_review_status() | |
|
1375 | PullRequestModel().update_reviewers( | |
|
1376 | pull_request, reviewers, self._rhodecode_db_user) | |
|
1377 | ||
|
1378 | Session().commit() | |
|
1379 | ||
|
1380 | msg = _('Pull request reviewers updated.') | |
|
1381 | h.flash(msg, category='success') | |
|
1382 | channelstream.pr_update_channelstream_push( | |
|
1383 | self.request, c.pr_broadcast_channel, self._rhodecode_user, msg) | |
|
1384 | ||
|
1385 | # trigger status changed if change in reviewers changes the status | |
|
1386 | calculated_status = pull_request.calculated_review_status() | |
|
1387 | if old_calculated_status != calculated_status: | |
|
1388 | PullRequestModel().trigger_pull_request_hook( | |
|
1389 | pull_request, self._rhodecode_user, 'review_status_change', | |
|
1390 | data={'status': calculated_status}) | |
|
1391 | ||
|
1392 | elif role == PullRequestReviewers.ROLE_OBSERVER: | |
|
1393 | try: | |
|
1394 | observers = validate_observers(review_members, reviewer_rules) | |
|
1395 | except ValueError as e: | |
|
1396 | log.error('Observers Validation: {}'.format(e)) | |
|
1397 | h.flash(e, category='error') | |
|
1398 | return | |
|
1399 | ||
|
1400 | PullRequestModel().update_observers( | |
|
1401 | pull_request, observers, self._rhodecode_db_user) | |
|
1402 | ||
|
1403 | Session().commit() | |
|
1404 | msg = _('Pull request observers updated.') | |
|
1405 | h.flash(msg, category='success') | |
|
1406 | channelstream.pr_update_channelstream_push( | |
|
1407 | self.request, c.pr_broadcast_channel, self._rhodecode_user, msg) | |
|
1408 | ||
|
1335 | 1409 | @LoginRequired() |
|
1336 | 1410 | @NotAnonymous() |
|
1337 | 1411 | @HasRepoPermissionAnyDecorator( |
@@ -1408,32 +1482,6 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1408 | 1482 | msg = merge_resp.merge_status_message |
|
1409 | 1483 | h.flash(msg, category='error') |
|
1410 | 1484 | |
|
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 | 1485 | @LoginRequired() |
|
1438 | 1486 | @NotAnonymous() |
|
1439 | 1487 | @HasRepoPermissionAnyDecorator( |
@@ -1488,8 +1536,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1488 | 1536 | allowed_to_comment = PullRequestModel().check_user_comment( |
|
1489 | 1537 | pull_request, self._rhodecode_user) |
|
1490 | 1538 | if not allowed_to_comment: |
|
1491 | log.debug( | |
|
1492 | 'comment: forbidden because pull request is from forbidden repo') | |
|
1539 | log.debug('comment: forbidden because pull request is from forbidden repo') | |
|
1493 | 1540 | raise HTTPForbidden() |
|
1494 | 1541 | |
|
1495 | 1542 | c = self.load_default_context() |
@@ -1518,6 +1565,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1518 | 1565 | pull_request, self._rhodecode_user, self.db_repo, message=text, |
|
1519 | 1566 | auth_user=self._rhodecode_user) |
|
1520 | 1567 | Session().flush() |
|
1568 | is_inline = comment.is_inline | |
|
1521 | 1569 | |
|
1522 | 1570 | PullRequestModel().trigger_pull_request_hook( |
|
1523 | 1571 | pull_request, self._rhodecode_user, 'comment', |
@@ -1551,6 +1599,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1551 | 1599 | resolves_comment_id=resolves_comment_id, |
|
1552 | 1600 | auth_user=self._rhodecode_user |
|
1553 | 1601 | ) |
|
1602 | is_inline = comment.is_inline | |
|
1554 | 1603 | |
|
1555 | 1604 | if allowed_to_change_status: |
|
1556 | 1605 | # calculate old status before we change it |
@@ -1599,6 +1648,16 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1599 | 1648 | data.update(comment.get_dict()) |
|
1600 | 1649 | data.update({'rendered_text': rendered_comment}) |
|
1601 | 1650 | |
|
1651 | comment_broadcast_channel = channelstream.comment_channel( | |
|
1652 | self.db_repo_name, pull_request_obj=pull_request) | |
|
1653 | ||
|
1654 | comment_data = data | |
|
1655 | comment_type = 'inline' if is_inline else 'general' | |
|
1656 | channelstream.comment_channelstream_push( | |
|
1657 | self.request, comment_broadcast_channel, self._rhodecode_user, | |
|
1658 | _('posted a new {} comment').format(comment_type), | |
|
1659 | comment_data=comment_data) | |
|
1660 | ||
|
1602 | 1661 | return data |
|
1603 | 1662 | |
|
1604 | 1663 | @LoginRequired() |
@@ -25,6 +25,7 b' from pyramid.view import view_config' | |||
|
25 | 25 | from rhodecode.apps._base import RepoAppView |
|
26 | 26 | from rhodecode.apps.repository.utils import get_default_reviewers_data |
|
27 | 27 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
28 | from rhodecode.lib.vcs.backends.base import Reference | |
|
28 | 29 | from rhodecode.model.db import Repository |
|
29 | 30 | |
|
30 | 31 | log = logging.getLogger(__name__) |
@@ -61,13 +62,28 b' class RepoReviewRulesView(RepoAppView):' | |||
|
61 | 62 | target_repo_name = request.GET.get('target_repo', source_repo_name) |
|
62 | 63 | target_repo = Repository.get_by_repo_name(target_repo_name) |
|
63 | 64 | |
|
64 | source_ref = request.GET['source_ref'] | |
|
65 | target_ref = request.GET['target_ref'] | |
|
66 |
source_commit = |
|
|
67 | target_commit = target_repo.get_commit(target_ref) | |
|
65 | current_user = request.user.get_instance() | |
|
66 | ||
|
67 | source_commit_id = request.GET['source_ref'] | |
|
68 | source_type = request.GET['source_ref_type'] | |
|
69 | source_name = request.GET['source_ref_name'] | |
|
70 | ||
|
71 | target_commit_id = request.GET['target_ref'] | |
|
72 | target_type = request.GET['target_ref_type'] | |
|
73 | target_name = request.GET['target_ref_name'] | |
|
68 | 74 | |
|
69 | current_user = request.user.get_instance() | |
|
75 | try: | |
|
70 | 76 | review_data = get_default_reviewers_data( |
|
71 | current_user, source_repo, source_commit, target_repo, target_commit) | |
|
77 | current_user, | |
|
78 | source_repo, | |
|
79 | Reference(source_type, source_name, source_commit_id), | |
|
80 | target_repo, | |
|
81 | Reference(target_type, target_name, target_commit_id) | |
|
82 | ) | |
|
83 | except ValueError: | |
|
84 | # No common ancestor | |
|
85 | msg = "No Common ancestor found between target and source reference" | |
|
86 | log.exception(msg) | |
|
87 | return {'diff_info': {'error': msg}} | |
|
72 | 88 | |
|
73 | 89 | return review_data |
@@ -341,6 +341,10 b' def includeme(config):' | |||
|
341 | 341 | name='json_ext', |
|
342 | 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 | 348 | # include RhodeCode plugins |
|
345 | 349 | includes = aslist(settings.get('rhodecode.includes', [])) |
|
346 | 350 | for inc in includes: |
@@ -408,6 +412,7 b' def sanitize_settings_and_apply_defaults' | |||
|
408 | 412 | """ |
|
409 | 413 | |
|
410 | 414 | settings.setdefault('rhodecode.edition', 'Community Edition') |
|
415 | settings.setdefault('rhodecode.edition_id', 'CE') | |
|
411 | 416 | |
|
412 | 417 | if 'mako.default_filters' not in settings: |
|
413 | 418 | # set custom default filters if we don't have it defined |
@@ -113,7 +113,7 b' def _commits_as_dict(event, commit_ids, ' | |||
|
113 | 113 | cs_data['permalink_url'] = RepoModel().get_commit_url( |
|
114 | 114 | repo, cs_data['raw_id'], request=event.request, |
|
115 | 115 | permalink=True) |
|
116 | urlified_message, issues_data = process_patterns( | |
|
116 | urlified_message, issues_data, errors = process_patterns( | |
|
117 | 117 | cs_data['message'], repo.repo_name) |
|
118 | 118 | cs_data['issues'] = issues_data |
|
119 | 119 | cs_data['message_html'] = urlify_commit_message( |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -88,6 +88,9 b' ACTIONS_V1 = {' | |||
|
88 | 88 | 'repo.pull_request.reviewer.add': '', |
|
89 | 89 | 'repo.pull_request.reviewer.delete': '', |
|
90 | 90 | |
|
91 | 'repo.pull_request.observer.add': '', | |
|
92 | 'repo.pull_request.observer.delete': '', | |
|
93 | ||
|
91 | 94 | 'repo.commit.strip': {'commit_id': ''}, |
|
92 | 95 | 'repo.commit.comment.create': {'data': {}}, |
|
93 | 96 | 'repo.commit.comment.delete': {'data': {}}, |
@@ -293,6 +293,7 b' def attach_context_attributes(context, r' | |||
|
293 | 293 | context.rc_config = rc_config |
|
294 | 294 | context.rhodecode_version = rhodecode.__version__ |
|
295 | 295 | context.rhodecode_edition = config.get('rhodecode.edition') |
|
296 | context.rhodecode_edition_id = config.get('rhodecode.edition_id') | |
|
296 | 297 | # unique secret + version does not leak the version but keep consistency |
|
297 | 298 | context.rhodecode_version_hash = calculate_version_hash(config) |
|
298 | 299 |
@@ -225,14 +225,26 b' def write_history(config, message):' | |||
|
225 | 225 | |
|
226 | 226 | def get_connection_validators(registry): |
|
227 | 227 | validators = [] |
|
228 |
for k, config in registry.rhodecode_plugins. |
|
|
228 | for k, config in registry.rhodecode_plugins.items(): | |
|
229 | 229 | validator = config.get('channelstream', {}).get('connect_validator') |
|
230 | 230 | if validator: |
|
231 | 231 | validators.append(validator) |
|
232 | 232 | return validators |
|
233 | 233 | |
|
234 | 234 | |
|
235 | def get_channelstream_config(registry=None): | |
|
236 | if not registry: | |
|
237 | registry = get_current_registry() | |
|
238 | ||
|
239 | rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {}) | |
|
240 | channelstream_config = rhodecode_plugins.get('channelstream', {}) | |
|
241 | return channelstream_config | |
|
242 | ||
|
243 | ||
|
235 | 244 | def post_message(channel, message, username, registry=None): |
|
245 | channelstream_config = get_channelstream_config(registry) | |
|
246 | if not channelstream_config.get('enabled'): | |
|
247 | return | |
|
236 | 248 | |
|
237 | 249 | message_obj = message |
|
238 | 250 | if isinstance(message, basestring): |
@@ -242,13 +254,7 b' def post_message(channel, message, usern' | |||
|
242 | 254 | 'topic': '/notifications' |
|
243 | 255 | } |
|
244 | 256 | |
|
245 | if not registry: | |
|
246 | registry = get_current_registry() | |
|
247 | ||
|
248 | 257 | log.debug('Channelstream: sending notification to channel %s', channel) |
|
249 | rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {}) | |
|
250 | channelstream_config = rhodecode_plugins.get('channelstream', {}) | |
|
251 | if channelstream_config.get('enabled'): | |
|
252 | 258 |
|
|
253 | 259 |
|
|
254 | 260 |
|
@@ -265,3 +271,101 b' def post_message(channel, message, usern' | |||
|
265 | 271 |
|
|
266 | 272 |
|
|
267 | 273 |
|
|
274 | ||
|
275 | ||
|
276 | def _reload_link(label): | |
|
277 | return ( | |
|
278 | '<a onclick="window.location.reload()">' | |
|
279 | '<strong>{}</strong>' | |
|
280 | '</a>'.format(label) | |
|
281 | ) | |
|
282 | ||
|
283 | ||
|
284 | def pr_channel(pull_request): | |
|
285 | repo_name = pull_request.target_repo.repo_name | |
|
286 | pull_request_id = pull_request.pull_request_id | |
|
287 | channel = '/repo${}$/pr/{}'.format(repo_name, pull_request_id) | |
|
288 | log.debug('Getting pull-request channelstream broadcast channel: %s', channel) | |
|
289 | return channel | |
|
290 | ||
|
291 | ||
|
292 | def comment_channel(repo_name, commit_obj=None, pull_request_obj=None): | |
|
293 | channel = None | |
|
294 | if commit_obj: | |
|
295 | channel = u'/repo${}$/commit/{}'.format( | |
|
296 | repo_name, commit_obj.raw_id | |
|
297 | ) | |
|
298 | elif pull_request_obj: | |
|
299 | channel = u'/repo${}$/pr/{}'.format( | |
|
300 | repo_name, pull_request_obj.pull_request_id | |
|
301 | ) | |
|
302 | log.debug('Getting comment channelstream broadcast channel: %s', channel) | |
|
303 | ||
|
304 | return channel | |
|
305 | ||
|
306 | ||
|
307 | def pr_update_channelstream_push(request, pr_broadcast_channel, user, msg, **kwargs): | |
|
308 | """ | |
|
309 | Channel push on pull request update | |
|
310 | """ | |
|
311 | if not pr_broadcast_channel: | |
|
312 | return | |
|
313 | ||
|
314 | _ = request.translate | |
|
315 | ||
|
316 | message = '{} {}'.format( | |
|
317 | msg, | |
|
318 | _reload_link(_(' Reload page to load changes'))) | |
|
319 | ||
|
320 | message_obj = { | |
|
321 | 'message': message, | |
|
322 | 'level': 'success', | |
|
323 | 'topic': '/notifications' | |
|
324 | } | |
|
325 | ||
|
326 | post_message( | |
|
327 | pr_broadcast_channel, message_obj, user.username, | |
|
328 | registry=request.registry) | |
|
329 | ||
|
330 | ||
|
331 | def comment_channelstream_push(request, comment_broadcast_channel, user, msg, **kwargs): | |
|
332 | """ | |
|
333 | Channelstream push on comment action, on commit, or pull-request | |
|
334 | """ | |
|
335 | if not comment_broadcast_channel: | |
|
336 | return | |
|
337 | ||
|
338 | _ = request.translate | |
|
339 | ||
|
340 | comment_data = kwargs.pop('comment_data', {}) | |
|
341 | user_data = kwargs.pop('user_data', {}) | |
|
342 | comment_id = comment_data.get('comment_id') | |
|
343 | ||
|
344 | message = '<strong>{}</strong> {} #{}, {}'.format( | |
|
345 | user.username, | |
|
346 | msg, | |
|
347 | comment_id, | |
|
348 | _reload_link(_('Reload page to see new comments')), | |
|
349 | ) | |
|
350 | ||
|
351 | message_obj = { | |
|
352 | 'message': message, | |
|
353 | 'level': 'success', | |
|
354 | 'topic': '/notifications' | |
|
355 | } | |
|
356 | ||
|
357 | post_message( | |
|
358 | comment_broadcast_channel, message_obj, user.username, | |
|
359 | registry=request.registry) | |
|
360 | ||
|
361 | message_obj = { | |
|
362 | 'message': None, | |
|
363 | 'user': user.username, | |
|
364 | 'comment_id': comment_id, | |
|
365 | 'comment_data': comment_data, | |
|
366 | 'user_data': user_data, | |
|
367 | 'topic': '/comment' | |
|
368 | } | |
|
369 | post_message( | |
|
370 | comment_broadcast_channel, message_obj, user.username, | |
|
371 | registry=request.registry) |
@@ -131,7 +131,7 b' def send_exc_email(request, exc_id, exc_' | |||
|
131 | 131 | |
|
132 | 132 | # NOTE(marcink): needed for email template rendering |
|
133 | 133 | user_id = None |
|
134 | if request: | |
|
134 | if hasattr(request, 'user'): | |
|
135 | 135 | user_id = request.user.user_id |
|
136 | 136 | attach_context_attributes(TemplateArgs(), request, user_id=user_id, is_api=True) |
|
137 | 137 |
@@ -38,6 +38,7 b' import re' | |||
|
38 | 38 | import time |
|
39 | 39 | import string |
|
40 | 40 | import hashlib |
|
41 | import regex | |
|
41 | 42 | from collections import OrderedDict |
|
42 | 43 | |
|
43 | 44 | import pygments |
@@ -1103,6 +1104,10 b' def bool2icon(value, show_at_false=True)' | |||
|
1103 | 1104 | return HTML.tag('i', class_="icon-false", title='False') |
|
1104 | 1105 | return HTML.tag('i') |
|
1105 | 1106 | |
|
1107 | ||
|
1108 | def b64(inp): | |
|
1109 | return base64.b64encode(inp) | |
|
1110 | ||
|
1106 | 1111 | #============================================================================== |
|
1107 | 1112 | # PERMS |
|
1108 | 1113 | #============================================================================== |
@@ -1653,7 +1658,7 b' def get_active_pattern_entries(repo_name' | |||
|
1653 | 1658 | return active_entries |
|
1654 | 1659 | |
|
1655 | 1660 | |
|
1656 | pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)') | |
|
1661 | pr_pattern_re = regex.compile(r'(?:(?:^!)|(?: !))(\d+)') | |
|
1657 | 1662 | |
|
1658 | 1663 | allowed_link_formats = [ |
|
1659 | 1664 | 'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] |
@@ -1670,6 +1675,7 b' def process_patterns(text_string, repo_n' | |||
|
1670 | 1675 | active_entries = get_active_pattern_entries(repo_name) |
|
1671 | 1676 | |
|
1672 | 1677 | issues_data = [] |
|
1678 | errors = [] | |
|
1673 | 1679 | new_text = text_string |
|
1674 | 1680 | |
|
1675 | 1681 | log.debug('Got %s entries to process', len(active_entries)) |
@@ -1687,9 +1693,11 b' def process_patterns(text_string, repo_n' | |||
|
1687 | 1693 | pattern = entry['pat_compiled'] |
|
1688 | 1694 | else: |
|
1689 | 1695 | try: |
|
1690 | pattern = re.compile(r'%s' % entry['pat']) | |
|
1691 | except re.error: | |
|
1692 | log.exception('issue tracker pattern: `%s` failed to compile', entry['pat']) | |
|
1696 | pattern = regex.compile(r'%s' % entry['pat']) | |
|
1697 | except regex.error as e: | |
|
1698 | regex_err = ValueError('{}:{}'.format(entry['pat'], e)) | |
|
1699 | log.exception('issue tracker pattern: `%s` failed to compile', regex_err) | |
|
1700 | errors.append(regex_err) | |
|
1693 | 1701 | continue |
|
1694 | 1702 | |
|
1695 | 1703 | data_func = partial( |
@@ -1721,11 +1729,11 b' def process_patterns(text_string, repo_n' | |||
|
1721 | 1729 | new_text = pr_pattern_re.sub(pr_url_func, new_text) |
|
1722 | 1730 | log.debug('processed !pr pattern') |
|
1723 | 1731 | |
|
1724 | return new_text, issues_data | |
|
1732 | return new_text, issues_data, errors | |
|
1725 | 1733 | |
|
1726 | 1734 | |
|
1727 | 1735 | def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None, |
|
1728 | issues_container=None): | |
|
1736 | issues_container=None, error_container=None): | |
|
1729 | 1737 | """ |
|
1730 | 1738 | Parses given text message and makes proper links. |
|
1731 | 1739 | issues are linked to given issue-server, and rest is a commit link |
@@ -1745,12 +1753,15 b' def urlify_commit_message(commit_text, r' | |||
|
1745 | 1753 | new_text = urlify_commits(new_text, repository) |
|
1746 | 1754 | |
|
1747 | 1755 | # process issue tracker patterns |
|
1748 |
new_text, issues = process_patterns( |
|
|
1749 |
|
|
|
1756 | new_text, issues, errors = process_patterns( | |
|
1757 | new_text, repository or '', active_entries=active_pattern_entries) | |
|
1750 | 1758 | |
|
1751 | 1759 | if issues_container is not None: |
|
1752 | 1760 | issues_container.extend(issues) |
|
1753 | 1761 | |
|
1762 | if error_container is not None: | |
|
1763 | error_container.extend(errors) | |
|
1764 | ||
|
1754 | 1765 | return literal(new_text) |
|
1755 | 1766 | |
|
1756 | 1767 | |
@@ -1805,7 +1816,7 b" def render(source, renderer='rst', menti" | |||
|
1805 | 1816 | elif renderer == 'rst': |
|
1806 | 1817 | if repo_name: |
|
1807 | 1818 | # process patterns on comments if we pass in repo name |
|
1808 | source, issues = process_patterns( | |
|
1819 | source, issues, errors = process_patterns( | |
|
1809 | 1820 | source, repo_name, link_format='rst', |
|
1810 | 1821 | active_entries=active_pattern_entries) |
|
1811 | 1822 | if issues_container is not None: |
@@ -1819,7 +1830,7 b" def render(source, renderer='rst', menti" | |||
|
1819 | 1830 | elif renderer == 'markdown': |
|
1820 | 1831 | if repo_name: |
|
1821 | 1832 | # process patterns on comments if we pass in repo name |
|
1822 | source, issues = process_patterns( | |
|
1833 | source, issues, errors = process_patterns( | |
|
1823 | 1834 | source, repo_name, link_format='markdown', |
|
1824 | 1835 | active_entries=active_pattern_entries) |
|
1825 | 1836 | if issues_container is not None: |
@@ -57,7 +57,43 b' FILEMODE_DEFAULT = 0o100644' | |||
|
57 | 57 | FILEMODE_EXECUTABLE = 0o100755 |
|
58 | 58 | EMPTY_COMMIT_ID = '0' * 40 |
|
59 | 59 | |
|
60 | Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id')) | |
|
60 | _Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id')) | |
|
61 | ||
|
62 | ||
|
63 | class Reference(_Reference): | |
|
64 | ||
|
65 | @property | |
|
66 | def branch(self): | |
|
67 | if self.type == 'branch': | |
|
68 | return self.name | |
|
69 | ||
|
70 | @property | |
|
71 | def bookmark(self): | |
|
72 | if self.type == 'book': | |
|
73 | return self.name | |
|
74 | ||
|
75 | ||
|
76 | def unicode_to_reference(raw): | |
|
77 | """ | |
|
78 | Convert a unicode (or string) to a reference object. | |
|
79 | If unicode evaluates to False it returns None. | |
|
80 | """ | |
|
81 | if raw: | |
|
82 | refs = raw.split(':') | |
|
83 | return Reference(*refs) | |
|
84 | else: | |
|
85 | return None | |
|
86 | ||
|
87 | ||
|
88 | def reference_to_unicode(ref): | |
|
89 | """ | |
|
90 | Convert a reference object to unicode. | |
|
91 | If reference is None it returns None. | |
|
92 | """ | |
|
93 | if ref: | |
|
94 | return u':'.join(ref) | |
|
95 | else: | |
|
96 | return None | |
|
61 | 97 | |
|
62 | 98 | |
|
63 | 99 | class MergeFailureReason(object): |
@@ -25,7 +25,7 b' import collections' | |||
|
25 | 25 | |
|
26 | 26 | from rhodecode.model import BaseModel |
|
27 | 27 | from rhodecode.model.db import ( |
|
28 | ChangesetStatus, ChangesetComment, PullRequest, Session) | |
|
28 | ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers, Session) | |
|
29 | 29 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError |
|
30 | 30 | from rhodecode.lib.markup_renderer import ( |
|
31 | 31 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
@@ -383,15 +383,14 b' class ChangesetStatusModel(BaseModel):' | |||
|
383 | 383 | pull_request.source_repo, |
|
384 | 384 | pull_request=pull_request, |
|
385 | 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) | |
|
388 | ||
|
389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): | |
|
390 | def calculated_review_status(self, pull_request): | |
|
390 | 391 | """ |
|
391 | 392 | calculate pull request status based on reviewers, it should be a list |
|
392 | 393 | of two element lists. |
|
393 | ||
|
394 | :param reviewers_statuses: | |
|
395 | 394 | """ |
|
396 |
reviewers = |
|
|
395 | reviewers = self.reviewers_statuses(pull_request) | |
|
397 | 396 | return self.calculate_status(reviewers) |
@@ -399,7 +399,7 b' class CommentsModel(BaseModel):' | |||
|
399 | 399 | recipients += [pull_request_obj.author] |
|
400 | 400 | |
|
401 | 401 | # add the reviewers to notification |
|
402 | recipients += [x.user for x in pull_request_obj.reviewers] | |
|
402 | recipients += [x.user for x in pull_request_obj.get_pull_request_reviewers()] | |
|
403 | 403 | |
|
404 | 404 | pr_target_repo = pull_request_obj.target_repo |
|
405 | 405 | pr_source_repo = pull_request_obj.source_repo |
@@ -436,9 +436,8 b' class CommentsModel(BaseModel):' | |||
|
436 | 436 | 'thread_ids': [pr_url, pr_comment_url], |
|
437 | 437 | }) |
|
438 | 438 | |
|
439 | if send_email: | |
|
439 | 440 | recipients += [self._get_user(u) for u in (extra_recipients or [])] |
|
440 | ||
|
441 | if send_email: | |
|
442 | 441 | # pre-generate the subject for notification itself |
|
443 | 442 | (subject, _e, body_plaintext) = EmailNotificationModel().render_email( |
|
444 | 443 | notification_type, **kwargs) |
@@ -463,55 +462,11 b' class CommentsModel(BaseModel):' | |||
|
463 | 462 | else: |
|
464 | 463 | action = 'repo.commit.comment.create' |
|
465 | 464 | |
|
466 | comment_id = comment.comment_id | |
|
467 | 465 | comment_data = comment.get_api_data() |
|
468 | 466 | |
|
469 | 467 | self._log_audit_action( |
|
470 | 468 | action, {'data': comment_data}, auth_user, comment) |
|
471 | 469 | |
|
472 | channel = None | |
|
473 | if commit_obj: | |
|
474 | repo_name = repo.repo_name | |
|
475 | channel = u'/repo${}$/commit/{}'.format( | |
|
476 | repo_name, | |
|
477 | commit_obj.raw_id | |
|
478 | ) | |
|
479 | elif pull_request_obj: | |
|
480 | repo_name = pr_target_repo.repo_name | |
|
481 | channel = u'/repo${}$/pr/{}'.format( | |
|
482 | repo_name, | |
|
483 | pull_request_obj.pull_request_id | |
|
484 | ) | |
|
485 | ||
|
486 | if channel: | |
|
487 | username = user.username | |
|
488 | message = '<strong>{}</strong> {} #{}, {}' | |
|
489 | message = message.format( | |
|
490 | username, | |
|
491 | _('posted a new comment'), | |
|
492 | comment_id, | |
|
493 | _('Refresh the page to see new comments.')) | |
|
494 | ||
|
495 | message_obj = { | |
|
496 | 'message': message, | |
|
497 | 'level': 'success', | |
|
498 | 'topic': '/notifications' | |
|
499 | } | |
|
500 | ||
|
501 | channelstream.post_message( | |
|
502 | channel, message_obj, user.username, | |
|
503 | registry=get_current_registry()) | |
|
504 | ||
|
505 | message_obj = { | |
|
506 | 'message': None, | |
|
507 | 'user': username, | |
|
508 | 'comment_id': comment_id, | |
|
509 | 'topic': '/comment' | |
|
510 | } | |
|
511 | channelstream.post_message( | |
|
512 | channel, message_obj, user.username, | |
|
513 | registry=get_current_registry()) | |
|
514 | ||
|
515 | 470 | return comment |
|
516 | 471 | |
|
517 | 472 | def edit(self, comment_id, text, auth_user, version): |
@@ -586,17 +541,20 b' class CommentsModel(BaseModel):' | |||
|
586 | 541 | |
|
587 | 542 | return comment |
|
588 | 543 | |
|
589 | def get_all_comments(self, repo_id, revision=None, pull_request=None): | |
|
544 | def get_all_comments(self, repo_id, revision=None, pull_request=None, count_only=False): | |
|
590 | 545 | q = ChangesetComment.query()\ |
|
591 | 546 | .filter(ChangesetComment.repo_id == repo_id) |
|
592 | 547 | if revision: |
|
593 | 548 | q = q.filter(ChangesetComment.revision == revision) |
|
594 | 549 | elif pull_request: |
|
595 | 550 | pull_request = self.__get_pull_request(pull_request) |
|
596 | q = q.filter(ChangesetComment.pull_request == pull_request) | |
|
551 | q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id) | |
|
597 | 552 | else: |
|
598 | 553 | raise Exception('Please specify commit or pull_request') |
|
599 | 554 | q = q.order_by(ChangesetComment.created_on) |
|
555 | if count_only: | |
|
556 | return q.count() | |
|
557 | ||
|
600 | 558 | return q.all() |
|
601 | 559 | |
|
602 | 560 | def get_url(self, comment, request=None, permalink=False, anchor=None): |
@@ -56,7 +56,8 b' from webhelpers2.text import remove_form' | |||
|
56 | 56 | |
|
57 | 57 | from rhodecode.translation import _ |
|
58 | 58 | from rhodecode.lib.vcs import get_vcs_instance, VCSError |
|
59 |
from rhodecode.lib.vcs.backends.base import |
|
|
59 | from rhodecode.lib.vcs.backends.base import ( | |
|
60 | EmptyCommit, Reference, unicode_to_reference, reference_to_unicode) | |
|
60 | 61 | from rhodecode.lib.utils2 import ( |
|
61 | 62 | str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe, |
|
62 | 63 | time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict, |
@@ -3773,12 +3774,12 b' class ChangesetComment(Base, BaseModel):' | |||
|
3773 | 3774 | resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by') |
|
3774 | 3775 | resolved_by = relationship('ChangesetComment', back_populates='resolved_comment') |
|
3775 | 3776 | |
|
3776 |
author = relationship('User', lazy=' |
|
|
3777 | author = relationship('User', lazy='select') | |
|
3777 | 3778 | repo = relationship('Repository') |
|
3778 |
status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy=' |
|
|
3779 |
pull_request = relationship('PullRequest', lazy=' |
|
|
3780 | pull_request_version = relationship('PullRequestVersion') | |
|
3781 |
history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy=' |
|
|
3779 | status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select') | |
|
3780 | pull_request = relationship('PullRequest', lazy='select') | |
|
3781 | pull_request_version = relationship('PullRequestVersion', lazy='select') | |
|
3782 | history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version') | |
|
3782 | 3783 | |
|
3783 | 3784 | @classmethod |
|
3784 | 3785 | def get_users(cls, revision=None, pull_request_id=None): |
@@ -3983,10 +3984,10 b' class ChangesetStatus(Base, BaseModel):' | |||
|
3983 | 3984 | version = Column('version', Integer(), nullable=False, default=0) |
|
3984 | 3985 | pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) |
|
3985 | 3986 | |
|
3986 |
author = relationship('User', lazy=' |
|
|
3987 | repo = relationship('Repository') | |
|
3988 |
comment = relationship('ChangesetComment', lazy=' |
|
|
3989 |
pull_request = relationship('PullRequest', lazy=' |
|
|
3987 | author = relationship('User', lazy='select') | |
|
3988 | repo = relationship('Repository', lazy='select') | |
|
3989 | comment = relationship('ChangesetComment', lazy='select') | |
|
3990 | pull_request = relationship('PullRequest', lazy='select') | |
|
3990 | 3991 | |
|
3991 | 3992 | def __unicode__(self): |
|
3992 | 3993 | return u"<%s('%s[v%s]:%s')>" % ( |
@@ -4248,26 +4249,11 b' class _PullRequestBase(BaseModel):' | |||
|
4248 | 4249 | |
|
4249 | 4250 | @staticmethod |
|
4250 | 4251 | def unicode_to_reference(raw): |
|
4251 | """ | |
|
4252 | Convert a unicode (or string) to a reference object. | |
|
4253 | If unicode evaluates to False it returns None. | |
|
4254 | """ | |
|
4255 | if raw: | |
|
4256 | refs = raw.split(':') | |
|
4257 | return Reference(*refs) | |
|
4258 | else: | |
|
4259 | return None | |
|
4252 | return unicode_to_reference(raw) | |
|
4260 | 4253 | |
|
4261 | 4254 | @staticmethod |
|
4262 | 4255 | def reference_to_unicode(ref): |
|
4263 | """ | |
|
4264 | Convert a reference object to unicode. | |
|
4265 | If reference is None it returns None. | |
|
4266 | """ | |
|
4267 | if ref: | |
|
4268 | return u':'.join(ref) | |
|
4269 | else: | |
|
4270 | return None | |
|
4256 | return reference_to_unicode(ref) | |
|
4271 | 4257 | |
|
4272 | 4258 | def get_api_data(self, with_merge_state=True): |
|
4273 | 4259 | from rhodecode.model.pull_request import PullRequestModel |
@@ -4465,6 +4451,37 b' class PullRequest(Base, _PullRequestBase' | |||
|
4465 | 4451 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
4466 | 4452 | return ChangesetStatusModel().reviewers_statuses(self) |
|
4467 | 4453 | |
|
4454 | def get_pull_request_reviewers(self, role=None): | |
|
4455 | qry = PullRequestReviewers.query()\ | |
|
4456 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id) | |
|
4457 | if role: | |
|
4458 | qry = qry.filter(PullRequestReviewers.role == role) | |
|
4459 | ||
|
4460 | return qry.all() | |
|
4461 | ||
|
4462 | @property | |
|
4463 | def reviewers_count(self): | |
|
4464 | qry = PullRequestReviewers.query()\ | |
|
4465 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |
|
4466 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER) | |
|
4467 | return qry.count() | |
|
4468 | ||
|
4469 | @property | |
|
4470 | def observers_count(self): | |
|
4471 | qry = PullRequestReviewers.query()\ | |
|
4472 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |
|
4473 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER) | |
|
4474 | return qry.count() | |
|
4475 | ||
|
4476 | def observers(self): | |
|
4477 | qry = PullRequestReviewers.query()\ | |
|
4478 | .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\ | |
|
4479 | .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\ | |
|
4480 | .all() | |
|
4481 | ||
|
4482 | for entry in qry: | |
|
4483 | yield entry, entry.user | |
|
4484 | ||
|
4468 | 4485 | @property |
|
4469 | 4486 | def workspace_id(self): |
|
4470 | 4487 | from rhodecode.model.pull_request import PullRequestModel |
@@ -4512,6 +4529,9 b' class PullRequestVersion(Base, _PullRequ' | |||
|
4512 | 4529 | @property |
|
4513 | 4530 | def reviewers(self): |
|
4514 | 4531 | return self.pull_request.reviewers |
|
4532 | @property | |
|
4533 | def reviewers(self): | |
|
4534 | return self.pull_request.reviewers | |
|
4515 | 4535 | |
|
4516 | 4536 | @property |
|
4517 | 4537 | def versions(self): |
@@ -4530,6 +4550,9 b' class PullRequestVersion(Base, _PullRequ' | |||
|
4530 | 4550 | def reviewers_statuses(self): |
|
4531 | 4551 | return self.pull_request.reviewers_statuses() |
|
4532 | 4552 | |
|
4553 | def observers(self): | |
|
4554 | return self.pull_request.observers() | |
|
4555 | ||
|
4533 | 4556 | |
|
4534 | 4557 | class PullRequestReviewers(Base, BaseModel): |
|
4535 | 4558 | __tablename__ = 'pull_request_reviewers' |
@@ -4538,6 +4561,7 b' class PullRequestReviewers(Base, BaseMod' | |||
|
4538 | 4561 | ) |
|
4539 | 4562 | ROLE_REVIEWER = u'reviewer' |
|
4540 | 4563 | ROLE_OBSERVER = u'observer' |
|
4564 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |
|
4541 | 4565 | |
|
4542 | 4566 | @hybrid_property |
|
4543 | 4567 | def reasons(self): |
@@ -4589,6 +4613,15 b' class PullRequestReviewers(Base, BaseMod' | |||
|
4589 | 4613 | |
|
4590 | 4614 | return user_group_data |
|
4591 | 4615 | |
|
4616 | @classmethod | |
|
4617 | def get_pull_request_reviewers(cls, pull_request_id, role=None): | |
|
4618 | qry = PullRequestReviewers.query()\ | |
|
4619 | .filter(PullRequestReviewers.pull_request_id == pull_request_id) | |
|
4620 | if role: | |
|
4621 | qry = qry.filter(PullRequestReviewers.role == role) | |
|
4622 | ||
|
4623 | return qry.all() | |
|
4624 | ||
|
4592 | 4625 | def __unicode__(self): |
|
4593 | 4626 | return u"<%s('id:%s')>" % (self.__class__.__name__, |
|
4594 | 4627 | self.pull_requests_reviewers_id) |
@@ -4954,16 +4987,21 b' class RepoReviewRuleUser(Base, BaseModel' | |||
|
4954 | 4987 | __table_args__ = ( |
|
4955 | 4988 | base_table_args |
|
4956 | 4989 | ) |
|
4990 | ROLE_REVIEWER = u'reviewer' | |
|
4991 | ROLE_OBSERVER = u'observer' | |
|
4992 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |
|
4957 | 4993 | |
|
4958 | 4994 | repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True) |
|
4959 | 4995 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) |
|
4960 | 4996 | user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False) |
|
4961 | 4997 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) |
|
4998 | role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER) | |
|
4962 | 4999 | user = relationship('User') |
|
4963 | 5000 | |
|
4964 | 5001 | def rule_data(self): |
|
4965 | 5002 | return { |
|
4966 | 'mandatory': self.mandatory | |
|
5003 | 'mandatory': self.mandatory, | |
|
5004 | 'role': self.role, | |
|
4967 | 5005 | } |
|
4968 | 5006 | |
|
4969 | 5007 | |
@@ -4974,17 +5012,22 b' class RepoReviewRuleUserGroup(Base, Base' | |||
|
4974 | 5012 | ) |
|
4975 | 5013 | |
|
4976 | 5014 | VOTE_RULE_ALL = -1 |
|
5015 | ROLE_REVIEWER = u'reviewer' | |
|
5016 | ROLE_OBSERVER = u'observer' | |
|
5017 | ROLES = [ROLE_REVIEWER, ROLE_OBSERVER] | |
|
4977 | 5018 | |
|
4978 | 5019 | repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True) |
|
4979 | 5020 | repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id')) |
|
4980 | 5021 | users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False) |
|
4981 | 5022 | mandatory = Column("mandatory", Boolean(), nullable=False, default=False) |
|
5023 | role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER) | |
|
4982 | 5024 | vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL) |
|
4983 | 5025 | users_group = relationship('UserGroup') |
|
4984 | 5026 | |
|
4985 | 5027 | def rule_data(self): |
|
4986 | 5028 | return { |
|
4987 | 5029 | 'mandatory': self.mandatory, |
|
5030 | 'role': self.role, | |
|
4988 | 5031 | 'vote_rule': self.vote_rule |
|
4989 | 5032 | } |
|
4990 | 5033 |
@@ -601,6 +601,14 b' def PullRequestForm(localizer, repo_id):' | |||
|
601 | 601 | reasons = All() |
|
602 | 602 | rules = All(v.UniqueList(localizer, convert=int)()) |
|
603 | 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 | 613 | class _PullRequestForm(formencode.Schema): |
|
606 | 614 | allow_extra_fields = True |
@@ -614,6 +622,7 b' def PullRequestForm(localizer, repo_id):' | |||
|
614 | 622 | revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(), |
|
615 | 623 | v.UniqueList(localizer)(not_empty=True)) |
|
616 | 624 | review_members = formencode.ForEach(ReviewerForm()) |
|
625 | observer_members = formencode.ForEach(ObserverForm()) | |
|
617 | 626 | pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255) |
|
618 | 627 | pullrequest_desc = v.UnicodeString(strip=True, required=False) |
|
619 | 628 | description_renderer = v.UnicodeString(strip=True, required=False) |
@@ -154,28 +154,56 b' def get_diff_info(' | |||
|
154 | 154 | |
|
155 | 155 | commits = [] |
|
156 | 156 | if get_commit_authors: |
|
157 | commits = target_scm.compare( | |
|
157 | log.debug('Obtaining commit authors from set of commits') | |
|
158 | _compare_data = target_scm.compare( | |
|
158 | 159 | target_ref, source_ref, source_scm, merge=True, |
|
159 |
pre_load=["author"] |
|
|
160 | pre_load=["author", "date", "message"] | |
|
161 | ) | |
|
160 | 162 | |
|
161 |
for commit in |
|
|
162 | user = User.get_from_cs_author(commit.author) | |
|
163 | for commit in _compare_data: | |
|
164 | # NOTE(marcink): we serialize here, so we don't produce more vcsserver calls on data returned | |
|
165 | # at this function which is later called via JSON serialization | |
|
166 | serialized_commit = dict( | |
|
167 | author=commit.author, | |
|
168 | date=commit.date, | |
|
169 | message=commit.message, | |
|
170 | commit_id=commit.raw_id, | |
|
171 | raw_id=commit.raw_id | |
|
172 | ) | |
|
173 | commits.append(serialized_commit) | |
|
174 | user = User.get_from_cs_author(serialized_commit['author']) | |
|
163 | 175 | if user and user not in commit_authors: |
|
164 | 176 | commit_authors.append(user) |
|
165 | 177 | |
|
166 | 178 | # lines |
|
167 | 179 | if get_authors: |
|
180 | log.debug('Calculating authors of changed files') | |
|
168 | 181 | target_commit = source_repo.get_commit(ancestor_id) |
|
169 | 182 | |
|
170 | 183 | for fname, lines in changed_lines.items(): |
|
184 | ||
|
171 | 185 | try: |
|
172 | node = target_commit.get_node(fname) | |
|
186 | node = target_commit.get_node(fname, pre_load=["is_binary"]) | |
|
173 | 187 | except Exception: |
|
188 | log.exception("Failed to load node with path %s", fname) | |
|
174 | 189 | continue |
|
175 | 190 | |
|
176 | 191 | if not isinstance(node, FileNode): |
|
177 | 192 | continue |
|
178 | 193 | |
|
194 | # NOTE(marcink): for binary node we don't do annotation, just use last author | |
|
195 | if node.is_binary: | |
|
196 | author = node.last_commit.author | |
|
197 | email = node.last_commit.author_email | |
|
198 | ||
|
199 | user = User.get_from_cs_author(author) | |
|
200 | if user: | |
|
201 | user_counts[user.user_id] = user_counts.get(user.user_id, 0) + 1 | |
|
202 | author_counts[author] = author_counts.get(author, 0) + 1 | |
|
203 | email_counts[email] = email_counts.get(email, 0) + 1 | |
|
204 | ||
|
205 | continue | |
|
206 | ||
|
179 | 207 | for annotation in node.annotate: |
|
180 | 208 | line_no, commit_id, get_commit_func, line_text = annotation |
|
181 | 209 | if line_no in lines: |
@@ -190,6 +218,8 b' def get_diff_info(' | |||
|
190 | 218 | author_counts[author] = author_counts.get(author, 0) + 1 |
|
191 | 219 | email_counts[email] = email_counts.get(email, 0) + 1 |
|
192 | 220 | |
|
221 | log.debug('Default reviewers processing finished') | |
|
222 | ||
|
193 | 223 | return { |
|
194 | 224 | 'commits': commits, |
|
195 | 225 | 'files': all_files_changes, |
@@ -260,10 +290,16 b' class PullRequestModel(BaseModel):' | |||
|
260 | 290 | _perms = ('repository.admin',) |
|
261 | 291 | return self._check_perms(_perms, pull_request, user) or owner |
|
262 | 292 | |
|
293 | def is_user_reviewer(self, pull_request, user): | |
|
294 | return user.user_id in [ | |
|
295 | x.user_id for x in | |
|
296 | pull_request.get_pull_request_reviewers(PullRequestReviewers.ROLE_REVIEWER) | |
|
297 | if x.user | |
|
298 | ] | |
|
299 | ||
|
263 | 300 | def check_user_change_status(self, pull_request, user, api=False): |
|
264 | reviewer = user.user_id in [x.user_id for x in | |
|
265 | pull_request.reviewers] | |
|
266 | return self.check_user_update(pull_request, user, api) or reviewer | |
|
301 | return self.check_user_update(pull_request, user, api) \ | |
|
302 | or self.is_user_reviewer(pull_request, user) | |
|
267 | 303 | |
|
268 | 304 | def check_user_comment(self, pull_request, user): |
|
269 | 305 | owner = user.user_id == pull_request.user_id |
@@ -575,7 +611,7 b' class PullRequestModel(BaseModel):' | |||
|
575 | 611 | pull_request_display_obj, at_version |
|
576 | 612 | |
|
577 | 613 | def create(self, created_by, source_repo, source_ref, target_repo, |
|
578 | target_ref, revisions, reviewers, title, description=None, | |
|
614 | target_ref, revisions, reviewers, observers, title, description=None, | |
|
579 | 615 | common_ancestor_id=None, |
|
580 | 616 | description_renderer=None, |
|
581 | 617 | reviewer_data=None, translator=None, auth_user=None): |
@@ -606,7 +642,7 b' class PullRequestModel(BaseModel):' | |||
|
606 | 642 | reviewer_ids = set() |
|
607 | 643 | # members / reviewers |
|
608 | 644 | for reviewer_object in reviewers: |
|
609 | user_id, reasons, mandatory, rules = reviewer_object | |
|
645 | user_id, reasons, mandatory, role, rules = reviewer_object | |
|
610 | 646 | user = self._get_user(user_id) |
|
611 | 647 | |
|
612 | 648 | # skip duplicates |
@@ -620,6 +656,7 b' class PullRequestModel(BaseModel):' | |||
|
620 | 656 | reviewer.pull_request = pull_request |
|
621 | 657 | reviewer.reasons = reasons |
|
622 | 658 | reviewer.mandatory = mandatory |
|
659 | reviewer.role = role | |
|
623 | 660 | |
|
624 | 661 | # NOTE(marcink): pick only first rule for now |
|
625 | 662 | rule_id = list(rules)[0] if rules else None |
@@ -653,6 +690,33 b' class PullRequestModel(BaseModel):' | |||
|
653 | 690 | Session().add(reviewer) |
|
654 | 691 | Session().flush() |
|
655 | 692 | |
|
693 | for observer_object in observers: | |
|
694 | user_id, reasons, mandatory, role, rules = observer_object | |
|
695 | user = self._get_user(user_id) | |
|
696 | ||
|
697 | # skip duplicates from reviewers | |
|
698 | if user.user_id in reviewer_ids: | |
|
699 | continue | |
|
700 | ||
|
701 | #reviewer_ids.add(user.user_id) | |
|
702 | ||
|
703 | observer = PullRequestReviewers() | |
|
704 | observer.user = user | |
|
705 | observer.pull_request = pull_request | |
|
706 | observer.reasons = reasons | |
|
707 | observer.mandatory = mandatory | |
|
708 | observer.role = role | |
|
709 | ||
|
710 | # NOTE(marcink): pick only first rule for now | |
|
711 | rule_id = list(rules)[0] if rules else None | |
|
712 | rule = RepoReviewRule.get(rule_id) if rule_id else None | |
|
713 | if rule: | |
|
714 | # TODO(marcink): do we need this for observers ?? | |
|
715 | pass | |
|
716 | ||
|
717 | Session().add(observer) | |
|
718 | Session().flush() | |
|
719 | ||
|
656 | 720 | # Set approval status to "Under Review" for all commits which are |
|
657 | 721 | # part of this pull request. |
|
658 | 722 | ChangesetStatusModel().set_status( |
@@ -678,7 +742,7 b' class PullRequestModel(BaseModel):' | |||
|
678 | 742 | MergeCheck.validate( |
|
679 | 743 | pull_request, auth_user=auth_user, translator=translator) |
|
680 | 744 | |
|
681 | self.notify_reviewers(pull_request, reviewer_ids) | |
|
745 | self.notify_reviewers(pull_request, reviewer_ids, created_by_user) | |
|
682 | 746 | self.trigger_pull_request_hook(pull_request, created_by_user, 'create') |
|
683 | 747 | |
|
684 | 748 | creation_data = pull_request.get_api_data(with_merge_state=False) |
@@ -1204,23 +1268,25 b' class PullRequestModel(BaseModel):' | |||
|
1204 | 1268 | |
|
1205 | 1269 | :param pull_request: the pr to update |
|
1206 | 1270 | :param reviewer_data: list of tuples |
|
1207 | [(user, ['reason1', 'reason2'], mandatory_flag, [rules])] | |
|
1271 | [(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])] | |
|
1272 | :param user: current use who triggers this action | |
|
1208 | 1273 | """ |
|
1274 | ||
|
1209 | 1275 | pull_request = self.__get_pull_request(pull_request) |
|
1210 | 1276 | if pull_request.is_closed(): |
|
1211 | 1277 | raise ValueError('This pull request is closed') |
|
1212 | 1278 | |
|
1213 | 1279 | reviewers = {} |
|
1214 | for user_id, reasons, mandatory, rules in reviewer_data: | |
|
1280 | for user_id, reasons, mandatory, role, rules in reviewer_data: | |
|
1215 | 1281 | if isinstance(user_id, (int, compat.string_types)): |
|
1216 | 1282 | user_id = self._get_user(user_id).user_id |
|
1217 | 1283 | reviewers[user_id] = { |
|
1218 | 'reasons': reasons, 'mandatory': mandatory} | |
|
1284 | 'reasons': reasons, 'mandatory': mandatory, 'role': role} | |
|
1219 | 1285 | |
|
1220 | 1286 | reviewers_ids = set(reviewers.keys()) |
|
1221 |
current_reviewers = PullRequestReviewers. |
|
|
1222 | .filter(PullRequestReviewers.pull_request == | |
|
1223 | pull_request).all() | |
|
1287 | current_reviewers = PullRequestReviewers.get_pull_request_reviewers( | |
|
1288 | pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER) | |
|
1289 | ||
|
1224 | 1290 | current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) |
|
1225 | 1291 | |
|
1226 | 1292 | ids_to_add = reviewers_ids.difference(current_reviewers_ids) |
@@ -1241,16 +1307,19 b' class PullRequestModel(BaseModel):' | |||
|
1241 | 1307 | reviewer.reasons = reviewers[uid]['reasons'] |
|
1242 | 1308 | # NOTE(marcink): mandatory shouldn't be changed now |
|
1243 | 1309 | # reviewer.mandatory = reviewers[uid]['reasons'] |
|
1310 | # NOTE(marcink): role should be hardcoded, so we won't edit it. | |
|
1311 | reviewer.role = PullRequestReviewers.ROLE_REVIEWER | |
|
1244 | 1312 | Session().add(reviewer) |
|
1245 | 1313 | added_audit_reviewers.append(reviewer.get_dict()) |
|
1246 | 1314 | |
|
1247 | 1315 | for uid in ids_to_remove: |
|
1248 | 1316 | changed = True |
|
1249 |
# NOTE(marcink): we fetch "ALL" reviewers using .all(). |
|
|
1250 |
# |
|
|
1317 | # NOTE(marcink): we fetch "ALL" reviewers objects using .all(). | |
|
1318 | # This is an edge case that handles previous state of having the same reviewer twice. | |
|
1251 | 1319 | # this CAN happen due to the lack of DB checks |
|
1252 | 1320 | reviewers = PullRequestReviewers.query()\ |
|
1253 | 1321 | .filter(PullRequestReviewers.user_id == uid, |
|
1322 | PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER, | |
|
1254 | 1323 | PullRequestReviewers.pull_request == pull_request)\ |
|
1255 | 1324 | .all() |
|
1256 | 1325 | |
@@ -1273,7 +1342,90 b' class PullRequestModel(BaseModel):' | |||
|
1273 | 1342 | 'repo.pull_request.reviewer.delete', {'old_data': user_data}, |
|
1274 | 1343 | user, pull_request) |
|
1275 | 1344 | |
|
1276 | self.notify_reviewers(pull_request, ids_to_add) | |
|
1345 | self.notify_reviewers(pull_request, ids_to_add, user) | |
|
1346 | return ids_to_add, ids_to_remove | |
|
1347 | ||
|
1348 | def update_observers(self, pull_request, observer_data, user): | |
|
1349 | """ | |
|
1350 | Update the observers in the pull request | |
|
1351 | ||
|
1352 | :param pull_request: the pr to update | |
|
1353 | :param observer_data: list of tuples | |
|
1354 | [(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])] | |
|
1355 | :param user: current use who triggers this action | |
|
1356 | """ | |
|
1357 | pull_request = self.__get_pull_request(pull_request) | |
|
1358 | if pull_request.is_closed(): | |
|
1359 | raise ValueError('This pull request is closed') | |
|
1360 | ||
|
1361 | observers = {} | |
|
1362 | for user_id, reasons, mandatory, role, rules in observer_data: | |
|
1363 | if isinstance(user_id, (int, compat.string_types)): | |
|
1364 | user_id = self._get_user(user_id).user_id | |
|
1365 | observers[user_id] = { | |
|
1366 | 'reasons': reasons, 'observers': mandatory, 'role': role} | |
|
1367 | ||
|
1368 | observers_ids = set(observers.keys()) | |
|
1369 | current_observers = PullRequestReviewers.get_pull_request_reviewers( | |
|
1370 | pull_request.pull_request_id, role=PullRequestReviewers.ROLE_OBSERVER) | |
|
1371 | ||
|
1372 | current_observers_ids = set([x.user.user_id for x in current_observers]) | |
|
1373 | ||
|
1374 | ids_to_add = observers_ids.difference(current_observers_ids) | |
|
1375 | ids_to_remove = current_observers_ids.difference(observers_ids) | |
|
1376 | ||
|
1377 | log.debug("Adding %s observer", ids_to_add) | |
|
1378 | log.debug("Removing %s observer", ids_to_remove) | |
|
1379 | changed = False | |
|
1380 | added_audit_observers = [] | |
|
1381 | removed_audit_observers = [] | |
|
1382 | ||
|
1383 | for uid in ids_to_add: | |
|
1384 | changed = True | |
|
1385 | _usr = self._get_user(uid) | |
|
1386 | observer = PullRequestReviewers() | |
|
1387 | observer.user = _usr | |
|
1388 | observer.pull_request = pull_request | |
|
1389 | observer.reasons = observers[uid]['reasons'] | |
|
1390 | # NOTE(marcink): mandatory shouldn't be changed now | |
|
1391 | # observer.mandatory = observer[uid]['reasons'] | |
|
1392 | ||
|
1393 | # NOTE(marcink): role should be hardcoded, so we won't edit it. | |
|
1394 | observer.role = PullRequestReviewers.ROLE_OBSERVER | |
|
1395 | Session().add(observer) | |
|
1396 | added_audit_observers.append(observer.get_dict()) | |
|
1397 | ||
|
1398 | for uid in ids_to_remove: | |
|
1399 | changed = True | |
|
1400 | # NOTE(marcink): we fetch "ALL" reviewers objects using .all(). | |
|
1401 | # This is an edge case that handles previous state of having the same reviewer twice. | |
|
1402 | # this CAN happen due to the lack of DB checks | |
|
1403 | observers = PullRequestReviewers.query()\ | |
|
1404 | .filter(PullRequestReviewers.user_id == uid, | |
|
1405 | PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER, | |
|
1406 | PullRequestReviewers.pull_request == pull_request)\ | |
|
1407 | .all() | |
|
1408 | ||
|
1409 | for obj in observers: | |
|
1410 | added_audit_observers.append(obj.get_dict()) | |
|
1411 | Session().delete(obj) | |
|
1412 | ||
|
1413 | if changed: | |
|
1414 | Session().expire_all() | |
|
1415 | pull_request.updated_on = datetime.datetime.now() | |
|
1416 | Session().add(pull_request) | |
|
1417 | ||
|
1418 | # finally store audit logs | |
|
1419 | for user_data in added_audit_observers: | |
|
1420 | self._log_audit_action( | |
|
1421 | 'repo.pull_request.observer.add', {'data': user_data}, | |
|
1422 | user, pull_request) | |
|
1423 | for user_data in removed_audit_observers: | |
|
1424 | self._log_audit_action( | |
|
1425 | 'repo.pull_request.observer.delete', {'old_data': user_data}, | |
|
1426 | user, pull_request) | |
|
1427 | ||
|
1428 | self.notify_observers(pull_request, ids_to_add, user) | |
|
1277 | 1429 | return ids_to_add, ids_to_remove |
|
1278 | 1430 | |
|
1279 | 1431 | def get_url(self, pull_request, request=None, permalink=False): |
@@ -1301,16 +1453,16 b' class PullRequestModel(BaseModel):' | |||
|
1301 | 1453 | pr_url = urllib.unquote(self.get_url(pull_request, request=request)) |
|
1302 | 1454 | return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url)) |
|
1303 | 1455 | |
|
1304 |
def notify_reviewers(self, pull_request, |
|
|
1305 | # notification to reviewers | |
|
1306 |
if not |
|
|
1456 | def _notify_reviewers(self, pull_request, user_ids, role, user): | |
|
1457 | # notification to reviewers/observers | |
|
1458 | if not user_ids: | |
|
1307 | 1459 | return |
|
1308 | 1460 | |
|
1309 |
log.debug('Notify following |
|
|
1461 | log.debug('Notify following %s users about pull-request %s', role, user_ids) | |
|
1310 | 1462 | |
|
1311 | 1463 | pull_request_obj = pull_request |
|
1312 | 1464 | # get the current participants of this pull request |
|
1313 |
recipients = |
|
|
1465 | recipients = user_ids | |
|
1314 | 1466 | notification_type = EmailNotificationModel.TYPE_PULL_REQUEST |
|
1315 | 1467 | |
|
1316 | 1468 | pr_source_repo = pull_request_obj.source_repo |
@@ -1332,8 +1484,10 b' class PullRequestModel(BaseModel):' | |||
|
1332 | 1484 | (x.raw_id, x.message) |
|
1333 | 1485 | for x in map(pr_source_repo.get_commit, pull_request.revisions)] |
|
1334 | 1486 | |
|
1487 | current_rhodecode_user = user | |
|
1335 | 1488 | kwargs = { |
|
1336 |
'user': |
|
|
1489 | 'user': current_rhodecode_user, | |
|
1490 | 'pull_request_author': pull_request.author, | |
|
1337 | 1491 | 'pull_request': pull_request_obj, |
|
1338 | 1492 | 'pull_request_commits': pull_request_commits, |
|
1339 | 1493 | |
@@ -1345,6 +1499,7 b' class PullRequestModel(BaseModel):' | |||
|
1345 | 1499 | |
|
1346 | 1500 | 'pull_request_url': pr_url, |
|
1347 | 1501 | 'thread_ids': [pr_url], |
|
1502 | 'user_role': role | |
|
1348 | 1503 | } |
|
1349 | 1504 | |
|
1350 | 1505 | # pre-generate the subject for notification itself |
@@ -1353,7 +1508,7 b' class PullRequestModel(BaseModel):' | |||
|
1353 | 1508 | |
|
1354 | 1509 | # create notification objects, and emails |
|
1355 | 1510 | NotificationModel().create( |
|
1356 |
created_by= |
|
|
1511 | created_by=current_rhodecode_user, | |
|
1357 | 1512 | notification_subject=subject, |
|
1358 | 1513 | notification_body=body_plaintext, |
|
1359 | 1514 | notification_type=notification_type, |
@@ -1361,11 +1516,19 b' class PullRequestModel(BaseModel):' | |||
|
1361 | 1516 | email_kwargs=kwargs, |
|
1362 | 1517 | ) |
|
1363 | 1518 | |
|
1519 | def notify_reviewers(self, pull_request, reviewers_ids, user): | |
|
1520 | return self._notify_reviewers(pull_request, reviewers_ids, | |
|
1521 | PullRequestReviewers.ROLE_REVIEWER, user) | |
|
1522 | ||
|
1523 | def notify_observers(self, pull_request, observers_ids, user): | |
|
1524 | return self._notify_reviewers(pull_request, observers_ids, | |
|
1525 | PullRequestReviewers.ROLE_OBSERVER, user) | |
|
1526 | ||
|
1364 | 1527 | def notify_users(self, pull_request, updating_user, ancestor_commit_id, |
|
1365 | 1528 | commit_changes, file_changes): |
|
1366 | 1529 | |
|
1367 | 1530 | updating_user_id = updating_user.user_id |
|
1368 | reviewers = set([x.user.user_id for x in pull_request.reviewers]) | |
|
1531 | reviewers = set([x.user.user_id for x in pull_request.get_pull_request_reviewers()]) | |
|
1369 | 1532 | # NOTE(marcink): send notification to all other users except to |
|
1370 | 1533 | # person who updated the PR |
|
1371 | 1534 | recipients = reviewers.difference(set([updating_user_id])) |
@@ -1874,11 +2037,13 b' class PullRequestModel(BaseModel):' | |||
|
1874 | 2037 | try: |
|
1875 | 2038 | from rc_reviewers.utils import get_default_reviewers_data |
|
1876 | 2039 | from rc_reviewers.utils import validate_default_reviewers |
|
2040 | from rc_reviewers.utils import validate_observers | |
|
1877 | 2041 | except ImportError: |
|
1878 | 2042 | from rhodecode.apps.repository.utils import get_default_reviewers_data |
|
1879 | 2043 | from rhodecode.apps.repository.utils import validate_default_reviewers |
|
2044 | from rhodecode.apps.repository.utils import validate_observers | |
|
1880 | 2045 | |
|
1881 | return get_default_reviewers_data, validate_default_reviewers | |
|
2046 | return get_default_reviewers_data, validate_default_reviewers, validate_observers | |
|
1882 | 2047 | |
|
1883 | 2048 | |
|
1884 | 2049 | class MergeCheck(object): |
@@ -42,7 +42,7 b' from rhodecode.lib.exceptions import (' | |||
|
42 | 42 | from rhodecode.lib.caching_query import FromCache |
|
43 | 43 | from rhodecode.model import BaseModel |
|
44 | 44 | from rhodecode.model.db import ( |
|
45 | _hash_key, true, false, or_, joinedload, User, UserToPerm, | |
|
45 | _hash_key, func, true, false, or_, joinedload, User, UserToPerm, | |
|
46 | 46 | UserEmailMap, UserIpMap, UserLog) |
|
47 | 47 | from rhodecode.model.meta import Session |
|
48 | 48 | from rhodecode.model.auth_token import AuthTokenModel |
@@ -96,7 +96,11 b' class UserModel(BaseModel):' | |||
|
96 | 96 | User.username.ilike(ilike_expression) |
|
97 | 97 | ) |
|
98 | 98 | ) |
|
99 | # sort by len to have top most matches first | |
|
100 | query = query.order_by(func.length(User.username))\ | |
|
101 | .order_by(User.username) | |
|
99 | 102 | query = query.limit(limit) |
|
103 | ||
|
100 | 104 | users = query.all() |
|
101 | 105 | |
|
102 | 106 | _users = [ |
@@ -21,12 +21,17 b'' | |||
|
21 | 21 | import colander |
|
22 | 22 | from rhodecode.model.validation_schema import validators, preparers, types |
|
23 | 23 | |
|
24 | DEFAULT_ROLE = 'reviewer' | |
|
25 | VALID_ROLES = ['reviewer', 'observer'] | |
|
26 | ||
|
24 | 27 | |
|
25 | 28 | class ReviewerSchema(colander.MappingSchema): |
|
26 | 29 | username = colander.SchemaNode(types.StrOrIntType()) |
|
27 | 30 | reasons = colander.SchemaNode(colander.List(), missing=['no reason specified']) |
|
28 | 31 | mandatory = colander.SchemaNode(colander.Boolean(), missing=False) |
|
29 | 32 | rules = colander.SchemaNode(colander.List(), missing=[]) |
|
33 | role = colander.SchemaNode(colander.String(), missing=DEFAULT_ROLE, | |
|
34 | validator=colander.OneOf(VALID_ROLES)) | |
|
30 | 35 | |
|
31 | 36 | |
|
32 | 37 | class ReviewerListSchema(colander.SequenceSchema): |
@@ -97,6 +97,7 b'' | |||
|
97 | 97 | <li>The server is being restarted.</li> |
|
98 | 98 | <li>The server is overloaded.</li> |
|
99 | 99 | <li>The link may be incorrect.</li> |
|
100 | <li><a onclick="window.location.reload()">Reload page</a></li> | |
|
100 | 101 | </ul> |
|
101 | 102 | </div> |
|
102 | 103 | <div class="inner-column"> |
@@ -374,9 +374,6 b' ul.auth_plugins {' | |||
|
374 | 374 | background-color: @grey6; |
|
375 | 375 | } |
|
376 | 376 | |
|
377 | .td-status { | |
|
378 | padding-left: .5em; | |
|
379 | } | |
|
380 | 377 | .log-container .truncate { |
|
381 | 378 | height: 2.75em; |
|
382 | 379 | white-space: pre-line; |
@@ -384,6 +381,10 b' ul.auth_plugins {' | |||
|
384 | 381 | table.rctable .user { |
|
385 | 382 | padding-left: 0; |
|
386 | 383 | } |
|
384 | .td-status { | |
|
385 | padding: 0 0px 0px 10px; | |
|
386 | width: 15px; | |
|
387 | } | |
|
387 | 388 | table.rctable { |
|
388 | 389 | td.td-description, |
|
389 | 390 | .rc-user { |
@@ -494,7 +495,8 b' ul.auth_plugins {' | |||
|
494 | 495 | padding-top: 10px; |
|
495 | 496 | } |
|
496 | 497 | |
|
497 |
#add_reviewer_input |
|
|
498 | #add_reviewer_input, | |
|
499 | #add_observer_input { | |
|
498 | 500 | padding-top: 10px |
|
499 | 501 | } |
|
500 | 502 | |
@@ -1700,8 +1702,33 b' table.group_members {' | |||
|
1700 | 1702 | } |
|
1701 | 1703 | |
|
1702 | 1704 | .reviewer_ac .ac-input { |
|
1705 | width: 98%; | |
|
1706 | margin-bottom: 1em; | |
|
1707 | } | |
|
1708 | ||
|
1709 | .observer_ac .ac-input { | |
|
1710 | width: 98%; | |
|
1711 | margin-bottom: 1em; | |
|
1712 | } | |
|
1713 | ||
|
1714 | .rule-table { | |
|
1703 | 1715 | width: 100%; |
|
1704 | margin-bottom: 1em; | |
|
1716 | } | |
|
1717 | ||
|
1718 | .rule-table td { | |
|
1719 | ||
|
1720 | } | |
|
1721 | ||
|
1722 | .rule-table .td-role { | |
|
1723 | width: 100px | |
|
1724 | } | |
|
1725 | ||
|
1726 | .rule-table .td-mandatory { | |
|
1727 | width: 100px | |
|
1728 | } | |
|
1729 | ||
|
1730 | .rule-table .td-group-votes { | |
|
1731 | width: 150px | |
|
1705 | 1732 | } |
|
1706 | 1733 | |
|
1707 | 1734 | .compare_view_commits tr{ |
@@ -2635,6 +2662,7 b' h3.files_location{' | |||
|
2635 | 2662 | li { |
|
2636 | 2663 | list-style-type: none |
|
2637 | 2664 | } |
|
2665 | ||
|
2638 | 2666 | } |
|
2639 | 2667 | |
|
2640 | 2668 | .grid-filter-box-icon { |
@@ -477,23 +477,3 b'' | |||
|
477 | 477 | } |
|
478 | 478 | |
|
479 | 479 | } |
|
480 | ||
|
481 | .rctable.repo_summary { | |
|
482 | border: 1px solid #eaeaea; | |
|
483 | border-radius: 2px; | |
|
484 | border-collapse: inherit; | |
|
485 | border-bottom: 0; | |
|
486 | ||
|
487 | th { | |
|
488 | background: @grey7; | |
|
489 | border-bottom: 0; | |
|
490 | } | |
|
491 | ||
|
492 | td { | |
|
493 | border-color: #eaeaea; | |
|
494 | } | |
|
495 | ||
|
496 | td.td-status { | |
|
497 | padding: 0 0 0 10px; | |
|
498 | } | |
|
499 | } |
@@ -4,6 +4,25 b'' | |||
|
4 | 4 | // see style guide documentation for guidelines. |
|
5 | 5 | |
|
6 | 6 | // TABLES |
|
7 | table.rctable.table-bordered { | |
|
8 | border: 1px solid #eaeaea; | |
|
9 | border-radius: 2px; | |
|
10 | border-collapse: inherit; | |
|
11 | border-bottom: 0; | |
|
12 | ||
|
13 | th { | |
|
14 | background: @grey7; | |
|
15 | border-bottom: 0; | |
|
16 | } | |
|
17 | ||
|
18 | td { | |
|
19 | border-color: #eaeaea; | |
|
20 | } | |
|
21 | ||
|
22 | td.td-status { | |
|
23 | padding: 0 0 0 10px; | |
|
24 | } | |
|
25 | } | |
|
7 | 26 | |
|
8 | 27 | .rctable, |
|
9 | 28 | table.rctable, |
@@ -306,12 +325,14 b' table.dataTable {' | |||
|
306 | 325 | } |
|
307 | 326 | } |
|
308 | 327 | } |
|
328 | ||
|
309 | 329 | .rctable.audit-log { |
|
310 | 330 | td { |
|
311 | 331 | vertical-align: top; |
|
312 | 332 | } |
|
313 | 333 | } |
|
314 | 334 | |
|
335 | ||
|
315 | 336 | // TRUNCATING |
|
316 | 337 | // TODO: lisaq: should this possibly be moved out of tables.less? |
|
317 | 338 | // for truncated text |
@@ -426,15 +447,6 b' table.keyboard-mappings {' | |||
|
426 | 447 | } |
|
427 | 448 | } |
|
428 | 449 | |
|
429 | // Pull Request List Table | |
|
430 | #pull_request_list_table.dataTable { | |
|
431 | ||
|
432 | //TODO: lisa: This needs to be removed once the description is adjusted | |
|
433 | // for using an expand_commit button (see issue 765) | |
|
434 | td { | |
|
435 | vertical-align: middle; | |
|
436 | } | |
|
437 | } | |
|
438 | 450 | |
|
439 | 451 | // Settings (no border) |
|
440 | 452 | table.rctable.dl-settings { |
@@ -484,9 +496,6 b' table.trending_language_tbl {' | |||
|
484 | 496 | |
|
485 | 497 | // Changesets |
|
486 | 498 | #changesets.rctable { |
|
487 | th { | |
|
488 | padding: 0 1em 0.65em 0; | |
|
489 | } | |
|
490 | 499 | |
|
491 | 500 | // td must be fixed height for graph |
|
492 | 501 | td { |
@@ -344,6 +344,10 b' mark,' | |||
|
344 | 344 | width: 200px; |
|
345 | 345 | } |
|
346 | 346 | |
|
347 | #obj_count { | |
|
348 | line-height: 34px; | |
|
349 | } | |
|
350 | ||
|
347 | 351 | } |
|
348 | 352 | |
|
349 | 353 | #readme .title { |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Delete', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Stop following this repository', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Submitting...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Löschen', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Stop following this repository', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Submitting...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Delete', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Stop following this repository', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Submitting...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Delete', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Stop following this repository', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Submitting...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Supprimer', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Arrêter de suivre ce dépôt', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Envoi…', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Aggiungi un altro commento', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Elimina', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'Nessun risultato', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Smetti di seguire il repository', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Inoltro...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'Al momento non ci sono richieste di PULL che richiedono il tuo intervento', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': '別のコメントを追加', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': '選択したステータス ({0}) を元にコメントが自動的に設定されます...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': '削除', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'まだリポジトリグループがありません。', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': '結果がありません', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'まだタグがありません。', |
|
69 | 74 | 'No user groups available yet.': 'まだユーザーグループがありません。', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'このリポジトリのフォローをやめる', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': '送信中...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -1,5 +1,7 b'' | |||
|
1 | 1 | // AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE |
|
2 | 2 | _gettext('(from usergroup {0})'); |
|
3 | _gettext('<strong>, and {0} file</strong> changed.'); | |
|
4 | _gettext('<strong>, and {0} files</strong> changed.'); | |
|
3 | 5 | _gettext('<strong>{0} file</strong> changed, '); |
|
4 | 6 | _gettext('<strong>{0} files</strong> changed, '); |
|
5 | 7 | _gettext('Add another comment'); |
@@ -21,6 +23,8 b'' | |||
|
21 | 23 | _gettext('Comment body was not changed.'); |
|
22 | 24 | _gettext('Comment text will be set automatically based on currently selected status ({0}) ...'); |
|
23 | 25 | _gettext('Commit Authors are not allowed to be a reviewer.'); |
|
26 | _gettext('Compare summary: <strong>{0} commit</strong>'); | |
|
27 | _gettext('Compare summary: <strong>{0} commits</strong>'); | |
|
24 | 28 | _gettext('Context file: '); |
|
25 | 29 | _gettext('Delete'); |
|
26 | 30 | _gettext('Delete this comment?'); |
@@ -58,6 +62,7 b'' | |||
|
58 | 62 | _gettext('No repository groups available yet.'); |
|
59 | 63 | _gettext('No repository groups present.'); |
|
60 | 64 | _gettext('No results'); |
|
65 | _gettext('No review rules set.'); | |
|
61 | 66 | _gettext('No ssh keys available yet.'); |
|
62 | 67 | _gettext('No tags available yet.'); |
|
63 | 68 | _gettext('No user groups available yet.'); |
@@ -95,11 +100,13 b'' | |||
|
95 | 100 | _gettext('Stop following this repository'); |
|
96 | 101 | _gettext('Stopped watching this repository'); |
|
97 | 102 | _gettext('Submitting...'); |
|
103 | _gettext('Switch target repository with the source.'); | |
|
98 | 104 | _gettext('Switch to chat'); |
|
99 | 105 | _gettext('Switch to comment'); |
|
100 | 106 | _gettext('TODO comment'); |
|
101 | 107 | _gettext('TODO from comment {0} was fixed.'); |
|
102 | 108 | _gettext('There are currently no open pull requests requiring your participation.'); |
|
109 | _gettext('There are no commits to merge.'); | |
|
103 | 110 | _gettext('There is a later version of file tree available. Click {0} to create a file at the latest tree.'); |
|
104 | 111 | _gettext('There is an existing path `{0}` at this commit.'); |
|
105 | 112 | _gettext('This pull requests will consist of <strong>{0} commit</strong>.'); |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Dodaj kolejny komentarz', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Usuń', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Zakończyć obserwację tego repozytorium', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Przesyłanie...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Adicionar outro comentário', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Excluir', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Parar de seguir este repositório', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Enviando...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Добавить другой комментарий', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': 'Удалить', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': 'Отменить наблюдение за репозиторием', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': 'Применение...', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -6,6 +6,8 b'' | |||
|
6 | 6 | //JS translations map |
|
7 | 7 | var _TM = { |
|
8 | 8 | '(from usergroup {0})': '(from usergroup {0})', |
|
9 | '<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.', | |
|
10 | '<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.', | |
|
9 | 11 | '<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ', |
|
10 | 12 | '<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ', |
|
11 | 13 | 'Add another comment': 'Add another comment', |
@@ -27,6 +29,8 b' var _TM = {' | |||
|
27 | 29 | 'Comment body was not changed.': 'Comment body was not changed.', |
|
28 | 30 | 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...', |
|
29 | 31 | 'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.', |
|
32 | 'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>', | |
|
33 | 'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>', | |
|
30 | 34 | 'Context file: ': 'Context file: ', |
|
31 | 35 | 'Delete': '删除', |
|
32 | 36 | 'Delete this comment?': 'Delete this comment?', |
@@ -64,6 +68,7 b' var _TM = {' | |||
|
64 | 68 | 'No repository groups available yet.': 'No repository groups available yet.', |
|
65 | 69 | 'No repository groups present.': 'No repository groups present.', |
|
66 | 70 | 'No results': 'No results', |
|
71 | 'No review rules set.': 'No review rules set.', | |
|
67 | 72 | 'No ssh keys available yet.': 'No ssh keys available yet.', |
|
68 | 73 | 'No tags available yet.': 'No tags available yet.', |
|
69 | 74 | 'No user groups available yet.': 'No user groups available yet.', |
@@ -101,11 +106,13 b' var _TM = {' | |||
|
101 | 106 | 'Stop following this repository': '停止关注该版本库', |
|
102 | 107 | 'Stopped watching this repository': 'Stopped watching this repository', |
|
103 | 108 | 'Submitting...': '提交中……', |
|
109 | 'Switch target repository with the source.': 'Switch target repository with the source.', | |
|
104 | 110 | 'Switch to chat': 'Switch to chat', |
|
105 | 111 | 'Switch to comment': 'Switch to comment', |
|
106 | 112 | 'TODO comment': 'TODO comment', |
|
107 | 113 | 'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.', |
|
108 | 114 | 'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.', |
|
115 | 'There are no commits to merge.': 'There are no commits to merge.', | |
|
109 | 116 | 'There is a later version of file tree available. Click {0} to create a file at the latest tree.': 'There is a later version of file tree available. Click {0} to create a file at the latest tree.', |
|
110 | 117 | 'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.', |
|
111 | 118 | 'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.', |
@@ -75,8 +75,8 b' var CommitsController = function () {' | |||
|
75 | 75 | height: height, |
|
76 | 76 | x_step: x_step, |
|
77 | 77 | y_step: 42, |
|
78 |
dotRadius: 3. |
|
|
79 |
lineWidth: 2. |
|
|
78 | dotRadius: 3.8, | |
|
79 | lineWidth: 2.8 | |
|
80 | 80 | }; |
|
81 | 81 | |
|
82 | 82 | var prevCommitsData = this.$graphCanvas.data('commits') || []; |
@@ -98,11 +98,12 b' var CommitsController = function () {' | |||
|
98 | 98 | |
|
99 | 99 | this.setLabelText(edgeData); |
|
100 | 100 | |
|
101 | var padding = 90; | |
|
101 | // main padding from top, aligns the first dot graph | |
|
102 | var padding = 100; | |
|
102 | 103 | if (prev_link) { |
|
103 | 104 | padding += 34; |
|
105 | } | |
|
104 | 106 | |
|
105 | } | |
|
106 | 107 | $('#graph_nodes').css({'padding-top': padding}); |
|
107 | 108 | |
|
108 | 109 | $.each($('.message.truncate'), function(idx, value) { |
@@ -94,21 +94,26 b' var getTitleAndDescription = function(so' | |||
|
94 | 94 | }; |
|
95 | 95 | |
|
96 | 96 | |
|
97 | ReviewersController = function () { | |
|
97 | window.ReviewersController = function () { | |
|
98 | 98 | var self = this; |
|
99 | this.$loadingIndicator = $('.calculate-reviewers'); | |
|
99 | 100 | this.$reviewRulesContainer = $('#review_rules'); |
|
100 | 101 | this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules'); |
|
101 | 102 | this.$userRule = $('.pr-user-rule-container'); |
|
102 | this.forbidReviewUsers = undefined; | |
|
103 | 103 | this.$reviewMembers = $('#review_members'); |
|
104 | this.$observerMembers = $('#observer_members'); | |
|
105 | ||
|
104 | 106 | this.currentRequest = null; |
|
105 | 107 | this.diffData = null; |
|
106 | 108 | this.enabledRules = []; |
|
109 | // sync with db.py entries | |
|
110 | this.ROLE_REVIEWER = 'reviewer'; | |
|
111 | this.ROLE_OBSERVER = 'observer' | |
|
107 | 112 | |
|
108 | 113 | //dummy handler, we might register our own later |
|
109 | 114 | this.diffDataHandler = function(data){}; |
|
110 | 115 | |
|
111 |
this.defaultForbid |
|
|
116 | this.defaultForbidUsers = function () { | |
|
112 | 117 | return [ |
|
113 | 118 | { |
|
114 | 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 | 128 | this.hideReviewRules = function () { |
|
121 | 129 | self.$reviewRulesContainer.hide(); |
|
122 | 130 | $(self.$userRule.selector).hide(); |
@@ -133,11 +141,40 b' ReviewersController = function () {' | |||
|
133 | 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 | 173 | this.loadReviewRules = function (data) { |
|
137 | 174 | self.diffData = data; |
|
138 | 175 | |
|
139 | 176 | // reset forbidden Users |
|
140 |
this.forbid |
|
|
177 | this.forbidUsers = self.defaultForbidUsers(); | |
|
141 | 178 | |
|
142 | 179 | // reset state of review rules |
|
143 | 180 | self.$rulesList.html(''); |
@@ -148,7 +185,7 b' ReviewersController = function () {' | |||
|
148 | 185 | self.addRule( |
|
149 | 186 | _gettext('All reviewers must vote.')) |
|
150 | 187 | ); |
|
151 |
return self.forbid |
|
|
188 | return self.forbidUsers | |
|
152 | 189 | } |
|
153 | 190 | |
|
154 | 191 | if (data.rules.voting !== undefined) { |
@@ -195,7 +232,7 b' ReviewersController = function () {' | |||
|
195 | 232 | } |
|
196 | 233 | |
|
197 | 234 | if (data.rules.forbid_author_to_review) { |
|
198 |
self.forbid |
|
|
235 | self.forbidUsers.push(data.rules_data.pr_author); | |
|
199 | 236 | self.$rulesList.append( |
|
200 | 237 | self.addRule( |
|
201 | 238 | _gettext('Author is not allowed to be a reviewer.')) |
@@ -206,9 +243,8 b' ReviewersController = function () {' | |||
|
206 | 243 | |
|
207 | 244 | if (data.rules_data.forbidden_users) { |
|
208 | 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 | 250 | self.$rulesList.append( |
@@ -223,9 +259,31 b' ReviewersController = function () {' | |||
|
223 | 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 | 287 | this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) { |
|
230 | 288 | |
|
231 | 289 | if (self.currentRequest) { |
@@ -233,19 +291,21 b' ReviewersController = function () {' | |||
|
233 | 291 | self.currentRequest.abort(); |
|
234 | 292 | } |
|
235 | 293 | |
|
236 | $('.calculate-reviewers').show(); | |
|
237 | // reset reviewer members | |
|
238 | self.$reviewMembers.empty(); | |
|
294 | self.$loadingIndicator.show(); | |
|
295 | ||
|
296 | // reset reviewer/observe members | |
|
297 | self.emptyTables(); | |
|
239 | 298 | |
|
240 | 299 | prButtonLock(true, null, 'reviewers'); |
|
241 | 300 | $('#user').hide(); // hide user autocomplete before load |
|
301 | $('#observer').hide(); //hide observer autocomplete before load | |
|
242 | 302 | |
|
243 | 303 | // lock PR button, so we cannot send PR before it's calculated |
|
244 | 304 | prButtonLock(true, _gettext('Loading diff ...'), 'compare'); |
|
245 | 305 | |
|
246 | 306 | if (sourceRef.length !== 3 || targetRef.length !== 3) { |
|
247 | 307 | // don't load defaults in case we're missing some refs... |
|
248 | $('.calculate-reviewers').hide(); | |
|
308 | self.$loadingIndicator.hide(); | |
|
249 | 309 | return |
|
250 | 310 | } |
|
251 | 311 | |
@@ -253,9 +313,13 b' ReviewersController = function () {' | |||
|
253 | 313 | { |
|
254 | 314 | 'repo_name': templateContext.repo_name, |
|
255 | 315 | 'source_repo': sourceRepo, |
|
316 | 'source_ref_type': sourceRef[0], | |
|
317 | 'source_ref_name': sourceRef[1], | |
|
256 | 318 | 'source_ref': sourceRef[2], |
|
257 | 319 | 'target_repo': targetRepo, |
|
258 | 'target_ref': targetRef[2] | |
|
320 | 'target_ref': targetRef[2], | |
|
321 | 'target_ref_type': sourceRef[0], | |
|
322 | 'target_ref_name': sourceRef[1] | |
|
259 | 323 | }); |
|
260 | 324 | |
|
261 | 325 | self.currentRequest = $.ajax({ |
@@ -268,15 +332,23 b' ReviewersController = function () {' | |||
|
268 | 332 | |
|
269 | 333 | // review rules |
|
270 | 334 | self.loadReviewRules(data); |
|
271 | self.handleDiffData(data["diff_info"]); | |
|
335 | var diffHandled = self.handleDiffData(data["diff_info"]); | |
|
336 | if (diffHandled === false) { | |
|
337 | return | |
|
338 | } | |
|
272 | 339 | |
|
273 | 340 | for (var i = 0; i < data.reviewers.length; i++) { |
|
274 | 341 | var reviewer = data.reviewers[i]; |
|
275 | self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory); | |
|
342 | // load reviewer rules from the repo data | |
|
343 | self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role); | |
|
276 | 344 | } |
|
277 | $('.calculate-reviewers').hide(); | |
|
345 | ||
|
346 | ||
|
347 | self.$loadingIndicator.hide(); | |
|
278 | 348 | prButtonLock(false, null, 'reviewers'); |
|
279 | $('#user').show(); // show user autocomplete after load | |
|
349 | ||
|
350 | $('#user').show(); // show user autocomplete before load | |
|
351 | $('#observer').show(); // show observer autocomplete before load | |
|
280 | 352 | |
|
281 | 353 | var commitElements = data["diff_info"]['commits']; |
|
282 | 354 | |
@@ -292,7 +364,7 b' ReviewersController = function () {' | |||
|
292 | 364 | |
|
293 | 365 | }, |
|
294 | 366 | error: function (jqXHR, textStatus, errorThrown) { |
|
295 | var prefix = "Loading diff and reviewers failed\n" | |
|
367 | var prefix = "Loading diff and reviewers/observers failed\n" | |
|
296 | 368 | var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix); |
|
297 | 369 | ajaxErrorSwal(message); |
|
298 | 370 | } |
@@ -301,7 +373,7 b' ReviewersController = function () {' | |||
|
301 | 373 | }; |
|
302 | 374 | |
|
303 | 375 | // check those, refactor |
|
304 |
this.remove |
|
|
376 | this.removeMember = function (reviewer_id, mark_delete) { | |
|
305 | 377 | var reviewer = $('#reviewer_{0}'.format(reviewer_id)); |
|
306 | 378 | |
|
307 | 379 | if (typeof (mark_delete) === undefined) { |
@@ -312,6 +384,7 b' ReviewersController = function () {' | |||
|
312 | 384 | if (reviewer) { |
|
313 | 385 | // now delete the input |
|
314 | 386 | $('#reviewer_{0} input'.format(reviewer_id)).remove(); |
|
387 | $('#reviewer_{0}_rules input'.format(reviewer_id)).remove(); | |
|
315 | 388 | // mark as to-delete |
|
316 | 389 | var obj = $('#reviewer_{0}_name'.format(reviewer_id)); |
|
317 | 390 | obj.addClass('to-delete'); |
@@ -322,27 +395,26 b' ReviewersController = function () {' | |||
|
322 | 395 | } |
|
323 | 396 | }; |
|
324 | 397 | |
|
325 | this.reviewMemberEntry = function () { | |
|
398 | this.addMember = function (reviewer_obj, reasons, mandatory, role) { | |
|
326 | 399 | |
|
327 | }; | |
|
328 | ||
|
329 | this.addReviewMember = function (reviewer_obj, reasons, mandatory) { | |
|
330 | 400 | var id = reviewer_obj.user_id; |
|
331 | 401 | var username = reviewer_obj.username; |
|
332 | 402 | |
|
333 |
|
|
|
334 |
|
|
|
403 | reasons = reasons || []; | |
|
404 | mandatory = mandatory || false; | |
|
405 | role = role || self.ROLE_REVIEWER | |
|
335 | 406 | |
|
336 | // register IDS to check if we don't have this ID already in | |
|
407 | // register current set IDS to check if we don't have this ID already in | |
|
408 | // and prevent duplicates | |
|
337 | 409 | var currentIds = []; |
|
338 | 410 | |
|
339 |
$.each( |
|
|
411 | $.each($('.reviewer_entry'), function (index, value) { | |
|
340 | 412 | currentIds.push($(value).data('reviewerUserId')) |
|
341 | 413 | }) |
|
342 | 414 | |
|
343 | 415 | var userAllowedReview = function (userId) { |
|
344 | 416 | var allowed = true; |
|
345 |
$.each(self.forbid |
|
|
417 | $.each(self.forbidUsers, function (index, member_data) { | |
|
346 | 418 | if (parseInt(userId) === member_data['user_id']) { |
|
347 | 419 | allowed = false; |
|
348 | 420 | return false // breaks the loop |
@@ -352,6 +424,7 b' ReviewersController = function () {' | |||
|
352 | 424 | }; |
|
353 | 425 | |
|
354 | 426 | var userAllowed = userAllowedReview(id); |
|
427 | ||
|
355 | 428 | if (!userAllowed) { |
|
356 | 429 | alert(_gettext('User `{0}` not allowed to be a reviewer').format(username)); |
|
357 | 430 | } else { |
@@ -359,11 +432,13 b' ReviewersController = function () {' | |||
|
359 | 432 | var alreadyReviewer = currentIds.indexOf(id) != -1; |
|
360 | 433 | |
|
361 | 434 | if (alreadyReviewer) { |
|
362 | alert(_gettext('User `{0}` already in reviewers').format(username)); | |
|
435 | alert(_gettext('User `{0}` already in reviewers/observers').format(username)); | |
|
363 | 436 | } else { |
|
437 | ||
|
364 | 438 | var reviewerEntry = renderTemplate('reviewMemberEntry', { |
|
365 | 439 | 'member': reviewer_obj, |
|
366 | 440 | 'mandatory': mandatory, |
|
441 | 'role': role, | |
|
367 | 442 | 'reasons': reasons, |
|
368 | 443 | 'allowed_to_update': true, |
|
369 | 444 | 'review_status': 'not_reviewed', |
@@ -372,20 +447,36 b' ReviewersController = function () {' | |||
|
372 | 447 | 'create': true, |
|
373 | 448 | 'rule_show': true, |
|
374 | 449 | }) |
|
450 | ||
|
451 | if (role === self.ROLE_REVIEWER) { | |
|
375 | 452 | $(self.$reviewMembers.selector).append(reviewerEntry); |
|
453 | self.increaseCounter(self.ROLE_REVIEWER); | |
|
454 | $('#reviewer-empty-msg').remove() | |
|
455 | } | |
|
456 | else if (role === self.ROLE_OBSERVER) { | |
|
457 | $(self.$observerMembers.selector).append(reviewerEntry); | |
|
458 | self.increaseCounter(self.ROLE_OBSERVER); | |
|
459 | $('#observer-empty-msg').remove(); | |
|
460 | } | |
|
461 | ||
|
376 | 462 | tooltipActivate(); |
|
377 | 463 | } |
|
378 | 464 | } |
|
379 | 465 | |
|
380 | 466 | }; |
|
381 | 467 | |
|
382 | this.updateReviewers = function (repo_name, pull_request_id) { | |
|
468 | this.updateReviewers = function (repo_name, pull_request_id, role) { | |
|
469 | if (role === 'reviewer') { | |
|
383 | 470 | var postData = $('#reviewers input').serialize(); |
|
384 | 471 | _updatePullRequest(repo_name, pull_request_id, postData); |
|
472 | } else if (role === 'observer') { | |
|
473 | var postData = $('#observers input').serialize(); | |
|
474 | _updatePullRequest(repo_name, pull_request_id, postData); | |
|
475 | } | |
|
385 | 476 | }; |
|
386 | 477 | |
|
387 | 478 | this.handleDiffData = function (data) { |
|
388 | self.diffDataHandler(data) | |
|
479 | return self.diffDataHandler(data) | |
|
389 | 480 | } |
|
390 | 481 | }; |
|
391 | 482 | |
@@ -449,23 +540,14 b' var editPullRequest = function(repo_name' | |||
|
449 | 540 | |
|
450 | 541 | |
|
451 | 542 | /** |
|
452 | * Reviewer autocomplete | |
|
543 | * autocomplete handler for reviewers/observers | |
|
453 | 544 | */ |
|
454 |
var |
|
|
455 | $(inputId).autocomplete({ | |
|
456 | serviceUrl: pyroutes.url('user_autocomplete_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) { | |
|
545 | var autoCompleteHandler = function (inputId, controller, role) { | |
|
546 | ||
|
547 | return function (element, data) { | |
|
467 | 548 | var mandatory = false; |
|
468 |
var reasons = [_gettext('added manually by "{0}"').format( |
|
|
549 | var reasons = [_gettext('added manually by "{0}"').format( | |
|
550 | templateContext.rhodecode_user.username)]; | |
|
469 | 551 | |
|
470 | 552 | // add whole user groups |
|
471 | 553 | if (data.value_type == 'user_group') { |
@@ -477,7 +559,7 b' var ReviewerAutoComplete = function(inpu' | |||
|
477 | 559 | reviewer['gravatar_link'] = member_data['icon_link']; |
|
478 | 560 | reviewer['user_link'] = member_data['profile_link']; |
|
479 | 561 | reviewer['rules'] = []; |
|
480 |
|
|
|
562 | controller.addMember(reviewer, reasons, mandatory, role); | |
|
481 | 563 | }) |
|
482 | 564 | } |
|
483 | 565 | // add single user |
@@ -487,14 +569,71 b' var ReviewerAutoComplete = function(inpu' | |||
|
487 | 569 | reviewer['gravatar_link'] = data['icon_link']; |
|
488 | 570 | reviewer['user_link'] = data['profile_link']; |
|
489 | 571 | reviewer['rules'] = []; |
|
490 |
|
|
|
572 | controller.addMember(reviewer, reasons, mandatory, role); | |
|
491 | 573 | } |
|
492 | 574 | |
|
493 | 575 | $(inputId).val(''); |
|
494 | 576 | } |
|
577 | } | |
|
578 | ||
|
579 | /** | |
|
580 | * Reviewer autocomplete | |
|
581 | */ | |
|
582 | var ReviewerAutoComplete = function (inputId, controller) { | |
|
583 | var self = this; | |
|
584 | self.controller = controller; | |
|
585 | self.inputId = inputId; | |
|
586 | var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER); | |
|
587 | ||
|
588 | $(inputId).autocomplete({ | |
|
589 | serviceUrl: pyroutes.url('user_autocomplete_data'), | |
|
590 | minChars: 2, | |
|
591 | maxHeight: 400, | |
|
592 | deferRequestBy: 300, //miliseconds | |
|
593 | showNoSuggestionNotice: true, | |
|
594 | tabDisabled: true, | |
|
595 | autoSelectFirst: true, | |
|
596 | params: { | |
|
597 | user_id: templateContext.rhodecode_user.user_id, | |
|
598 | user_groups: true, | |
|
599 | user_groups_expand: true, | |
|
600 | skip_default_user: true | |
|
601 | }, | |
|
602 | formatResult: autocompleteFormatResult, | |
|
603 | lookupFilter: autocompleteFilterResult, | |
|
604 | onSelect: handler | |
|
495 | 605 | }); |
|
496 | 606 | }; |
|
497 | 607 | |
|
608 | /** | |
|
609 | * Observers autocomplete | |
|
610 | */ | |
|
611 | var ObserverAutoComplete = function(inputId, controller) { | |
|
612 | var self = this; | |
|
613 | self.controller = controller; | |
|
614 | self.inputId = inputId; | |
|
615 | var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER); | |
|
616 | ||
|
617 | $(inputId).autocomplete({ | |
|
618 | serviceUrl: pyroutes.url('user_autocomplete_data'), | |
|
619 | minChars: 2, | |
|
620 | maxHeight: 400, | |
|
621 | deferRequestBy: 300, //miliseconds | |
|
622 | showNoSuggestionNotice: true, | |
|
623 | tabDisabled: true, | |
|
624 | autoSelectFirst: true, | |
|
625 | params: { | |
|
626 | user_id: templateContext.rhodecode_user.user_id, | |
|
627 | user_groups: true, | |
|
628 | user_groups_expand: true, | |
|
629 | skip_default_user: true | |
|
630 | }, | |
|
631 | formatResult: autocompleteFormatResult, | |
|
632 | lookupFilter: autocompleteFilterResult, | |
|
633 | onSelect: handler | |
|
634 | }); | |
|
635 | } | |
|
636 | ||
|
498 | 637 | |
|
499 | 638 | window.VersionController = function () { |
|
500 | 639 | var self = this; |
@@ -504,7 +643,7 b' window.VersionController = function () {' | |||
|
504 | 643 | |
|
505 | 644 | this.adjustRadioSelectors = function (curNode) { |
|
506 | 645 | var getVal = function (item) { |
|
507 | if (item == 'latest') { | |
|
646 | if (item === 'latest') { | |
|
508 | 647 | return Number.MAX_SAFE_INTEGER |
|
509 | 648 | } |
|
510 | 649 | else { |
@@ -663,6 +802,7 b' window.UpdatePrController = function () ' | |||
|
663 | 802 | }; |
|
664 | 803 | }; |
|
665 | 804 | |
|
805 | ||
|
666 | 806 | /** |
|
667 | 807 | * Reviewer display panel |
|
668 | 808 | */ |
@@ -673,6 +813,7 b' window.ReviewersPanel = {' | |||
|
673 | 813 | removeButtons: null, |
|
674 | 814 | reviewRules: null, |
|
675 | 815 | setReviewers: null, |
|
816 | controller: null, | |
|
676 | 817 | |
|
677 | 818 | setSelectors: function () { |
|
678 | 819 | var self = this; |
@@ -682,17 +823,18 b' window.ReviewersPanel = {' | |||
|
682 | 823 | self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove'); |
|
683 | 824 | }, |
|
684 | 825 | |
|
685 | init: function (reviewRules, setReviewers) { | |
|
826 | init: function (controller, reviewRules, setReviewers) { | |
|
686 | 827 | var self = this; |
|
687 | 828 | self.setSelectors(); |
|
688 | 829 | |
|
689 | this.reviewRules = reviewRules; | |
|
690 |
|
|
|
830 | self.controller = controller; | |
|
831 | self.reviewRules = reviewRules; | |
|
832 | self.setReviewers = setReviewers; | |
|
691 | 833 | |
|
692 |
|
|
|
834 | self.editButton.on('click', function (e) { | |
|
693 | 835 | self.edit(); |
|
694 | 836 | }); |
|
695 |
|
|
|
837 | self.closeButton.on('click', function (e) { | |
|
696 | 838 | self.close(); |
|
697 | 839 | self.renderReviewers(); |
|
698 | 840 | }); |
@@ -702,14 +844,26 b' window.ReviewersPanel = {' | |||
|
702 | 844 | }, |
|
703 | 845 | |
|
704 | 846 | renderReviewers: function () { |
|
847 | var self = this; | |
|
705 | 848 | |
|
706 | $('#review_members').html('') | |
|
707 | $.each(this.setReviewers.reviewers, function (key, val) { | |
|
849 | if (self.setReviewers.reviewers === undefined) { | |
|
850 | return | |
|
851 | } | |
|
852 | if (self.setReviewers.reviewers.length === 0) { | |
|
853 | self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>'); | |
|
854 | return | |
|
855 | } | |
|
856 | ||
|
857 | self.controller.emptyReviewersTable(); | |
|
858 | ||
|
859 | $.each(self.setReviewers.reviewers, function (key, val) { | |
|
860 | ||
|
708 | 861 | var member = val; |
|
709 | ||
|
862 | if (member.role === self.controller.ROLE_REVIEWER) { | |
|
710 | 863 | var entry = renderTemplate('reviewMemberEntry', { |
|
711 | 864 | 'member': member, |
|
712 | 865 | 'mandatory': member.mandatory, |
|
866 | 'role': member.role, | |
|
713 | 867 | 'reasons': member.reasons, |
|
714 | 868 | 'allowed_to_update': member.allowed_to_update, |
|
715 | 869 | 'review_status': member.review_status, |
@@ -718,10 +872,106 b' window.ReviewersPanel = {' | |||
|
718 | 872 | 'create': false |
|
719 | 873 | }); |
|
720 | 874 | |
|
721 |
$( |
|
|
875 | $(self.controller.$reviewMembers.selector).append(entry) | |
|
876 | } | |
|
722 | 877 | }); |
|
878 | ||
|
723 | 879 | tooltipActivate(); |
|
880 | }, | |
|
724 | 881 | |
|
882 | edit: function (event) { | |
|
883 | var self = this; | |
|
884 | self.editButton.hide(); | |
|
885 | self.closeButton.show(); | |
|
886 | self.addButton.show(); | |
|
887 | $(self.removeButtons.selector).css('visibility', 'visible'); | |
|
888 | // review rules | |
|
889 | self.controller.loadReviewRules(this.reviewRules); | |
|
890 | }, | |
|
891 | ||
|
892 | close: function (event) { | |
|
893 | var self = this; | |
|
894 | this.editButton.show(); | |
|
895 | this.closeButton.hide(); | |
|
896 | this.addButton.hide(); | |
|
897 | $(this.removeButtons.selector).css('visibility', 'hidden'); | |
|
898 | // hide review rules | |
|
899 | self.controller.hideReviewRules(); | |
|
900 | } | |
|
901 | }; | |
|
902 | ||
|
903 | /** | |
|
904 | * Reviewer display panel | |
|
905 | */ | |
|
906 | window.ObserversPanel = { | |
|
907 | editButton: null, | |
|
908 | closeButton: null, | |
|
909 | addButton: null, | |
|
910 | removeButtons: null, | |
|
911 | reviewRules: null, | |
|
912 | setReviewers: null, | |
|
913 | controller: null, | |
|
914 | ||
|
915 | setSelectors: function () { | |
|
916 | var self = this; | |
|
917 | self.editButton = $('#open_edit_observers'); | |
|
918 | self.closeButton =$('#close_edit_observers'); | |
|
919 | self.addButton = $('#add_observer'); | |
|
920 | self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove'); | |
|
921 | }, | |
|
922 | ||
|
923 | init: function (controller, reviewRules, setReviewers) { | |
|
924 | var self = this; | |
|
925 | self.setSelectors(); | |
|
926 | ||
|
927 | self.controller = controller; | |
|
928 | self.reviewRules = reviewRules; | |
|
929 | self.setReviewers = setReviewers; | |
|
930 | ||
|
931 | self.editButton.on('click', function (e) { | |
|
932 | self.edit(); | |
|
933 | }); | |
|
934 | self.closeButton.on('click', function (e) { | |
|
935 | self.close(); | |
|
936 | self.renderObservers(); | |
|
937 | }); | |
|
938 | ||
|
939 | self.renderObservers(); | |
|
940 | ||
|
941 | }, | |
|
942 | ||
|
943 | renderObservers: function () { | |
|
944 | var self = this; | |
|
945 | if (self.setReviewers.observers === undefined) { | |
|
946 | return | |
|
947 | } | |
|
948 | if (self.setReviewers.observers.length === 0) { | |
|
949 | self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>'); | |
|
950 | return | |
|
951 | } | |
|
952 | ||
|
953 | self.controller.emptyObserversTable(); | |
|
954 | ||
|
955 | $.each(self.setReviewers.observers, function (key, val) { | |
|
956 | var member = val; | |
|
957 | if (member.role === self.controller.ROLE_OBSERVER) { | |
|
958 | var entry = renderTemplate('reviewMemberEntry', { | |
|
959 | 'member': member, | |
|
960 | 'mandatory': member.mandatory, | |
|
961 | 'role': member.role, | |
|
962 | 'reasons': member.reasons, | |
|
963 | 'allowed_to_update': member.allowed_to_update, | |
|
964 | 'review_status': member.review_status, | |
|
965 | 'review_status_label': member.review_status_label, | |
|
966 | 'user_group': member.user_group, | |
|
967 | 'create': false | |
|
968 | }); | |
|
969 | ||
|
970 | $(self.controller.$observerMembers.selector).append(entry) | |
|
971 | } | |
|
972 | }); | |
|
973 | ||
|
974 | tooltipActivate(); | |
|
725 | 975 | }, |
|
726 | 976 | |
|
727 | 977 | edit: function (event) { |
@@ -729,8 +979,6 b' window.ReviewersPanel = {' | |||
|
729 | 979 | this.closeButton.show(); |
|
730 | 980 | this.addButton.show(); |
|
731 | 981 | $(this.removeButtons.selector).css('visibility', 'visible'); |
|
732 | // review rules | |
|
733 | reviewersController.loadReviewRules(this.reviewRules); | |
|
734 | 982 | }, |
|
735 | 983 | |
|
736 | 984 | close: function (event) { |
@@ -738,12 +986,56 b' window.ReviewersPanel = {' | |||
|
738 | 986 | this.closeButton.hide(); |
|
739 | 987 | this.addButton.hide(); |
|
740 | 988 | $(this.removeButtons.selector).css('visibility', 'hidden'); |
|
741 | // hide review rules | |
|
742 | reviewersController.hideReviewRules() | |
|
989 | } | |
|
990 | ||
|
991 | }; | |
|
992 | ||
|
993 | window.PRDetails = { | |
|
994 | editButton: null, | |
|
995 | closeButton: null, | |
|
996 | deleteButton: null, | |
|
997 | viewFields: null, | |
|
998 | editFields: null, | |
|
999 | ||
|
1000 | setSelectors: function () { | |
|
1001 | var self = this; | |
|
1002 | self.editButton = $('#open_edit_pullrequest') | |
|
1003 | self.closeButton = $('#close_edit_pullrequest') | |
|
1004 | self.deleteButton = $('#delete_pullrequest') | |
|
1005 | self.viewFields = $('#pr-desc, #pr-title') | |
|
1006 | self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save') | |
|
1007 | }, | |
|
1008 | ||
|
1009 | init: function () { | |
|
1010 | var self = this; | |
|
1011 | self.setSelectors(); | |
|
1012 | self.editButton.on('click', function (e) { | |
|
1013 | self.edit(); | |
|
1014 | }); | |
|
1015 | self.closeButton.on('click', function (e) { | |
|
1016 | self.view(); | |
|
1017 | }); | |
|
1018 | }, | |
|
1019 | ||
|
1020 | edit: function (event) { | |
|
1021 | var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm; | |
|
1022 | this.viewFields.hide(); | |
|
1023 | this.editButton.hide(); | |
|
1024 | this.deleteButton.hide(); | |
|
1025 | this.closeButton.show(); | |
|
1026 | this.editFields.show(); | |
|
1027 | cmInstance.refresh(); | |
|
1028 | }, | |
|
1029 | ||
|
1030 | view: function (event) { | |
|
1031 | this.editButton.show(); | |
|
1032 | this.deleteButton.show(); | |
|
1033 | this.editFields.hide(); | |
|
1034 | this.closeButton.hide(); | |
|
1035 | this.viewFields.show(); | |
|
743 | 1036 | } |
|
744 | 1037 | }; |
|
745 | 1038 | |
|
746 | ||
|
747 | 1039 | /** |
|
748 | 1040 | * OnLine presence using channelstream |
|
749 | 1041 | */ |
@@ -813,17 +1105,14 b' window.refreshComments = function (versi' | |||
|
813 | 1105 | $.each($('.comment'), function (idx, element) { |
|
814 | 1106 | currentIDs.push($(element).data('commentId')); |
|
815 | 1107 | }); |
|
816 |
var data = {"comments |
|
|
1108 | var data = {"comments": currentIDs}; | |
|
817 | 1109 | |
|
818 | 1110 | var $targetElem = $('.comments-content-table'); |
|
819 | 1111 | $targetElem.css('opacity', 0.3); |
|
820 | $targetElem.load( | |
|
821 | loadUrl, data, function (responseText, textStatus, jqXHR) { | |
|
822 | if (jqXHR.status !== 200) { | |
|
823 | return false; | |
|
824 | } | |
|
1112 | ||
|
1113 | var success = function (data) { | |
|
825 | 1114 |
|
|
826 |
|
|
|
1115 | var newCount = $(data).data('counter'); | |
|
827 | 1116 |
|
|
828 | 1117 |
|
|
829 | 1118 |
|
@@ -833,9 +1122,12 b' window.refreshComments = function (versi' | |||
|
833 | 1122 |
|
|
834 | 1123 | |
|
835 | 1124 |
|
|
1125 | $targetElem.html(data); | |
|
836 | 1126 |
|
|
837 | 1127 |
|
|
838 | ); | |
|
1128 | ||
|
1129 | ajaxPOST(loadUrl, data, success, null, {}) | |
|
1130 | ||
|
839 | 1131 | } |
|
840 | 1132 | |
|
841 | 1133 | window.refreshTODOs = function (version) { |
@@ -858,16 +1150,13 b' window.refreshTODOs = function (version)' | |||
|
858 | 1150 | currentIDs.push($(element).data('commentId')); |
|
859 | 1151 | }); |
|
860 | 1152 | |
|
861 |
var data = {"comments |
|
|
1153 | var data = {"comments": currentIDs}; | |
|
862 | 1154 | var $targetElem = $('.todos-content-table'); |
|
863 | 1155 | $targetElem.css('opacity', 0.3); |
|
864 | $targetElem.load( | |
|
865 | loadUrl, data, function (responseText, textStatus, jqXHR) { | |
|
866 | if (jqXHR.status !== 200) { | |
|
867 | return false; | |
|
868 | } | |
|
1156 | ||
|
1157 | var success = function (data) { | |
|
869 | 1158 |
|
|
870 |
|
|
|
1159 | var newCount = $(data).data('counter'); | |
|
871 | 1160 |
|
|
872 | 1161 |
|
|
873 | 1162 |
|
@@ -877,9 +1166,12 b' window.refreshTODOs = function (version)' | |||
|
877 | 1166 |
|
|
878 | 1167 | |
|
879 | 1168 |
|
|
1169 | $targetElem.html(data); | |
|
880 | 1170 |
|
|
881 | 1171 |
|
|
882 | ); | |
|
1172 | ||
|
1173 | ajaxPOST(loadUrl, data, success, null, {}) | |
|
1174 | ||
|
883 | 1175 | } |
|
884 | 1176 | |
|
885 | 1177 | window.refreshAllComments = function (version) { |
@@ -888,3 +1180,12 b' window.refreshAllComments = function (ve' | |||
|
888 | 1180 | refreshComments(version); |
|
889 | 1181 | refreshTODOs(version); |
|
890 | 1182 | }; |
|
1183 | ||
|
1184 | window.sidebarComment = function (commentId) { | |
|
1185 | var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64'); | |
|
1186 | if (!jsonData) { | |
|
1187 | return 'Failed to load comment {0}'.format(commentId) | |
|
1188 | } | |
|
1189 | var funcData = JSON.parse(atob(jsonData)); | |
|
1190 | return renderTemplate('sideBarCommentHovercard', funcData) | |
|
1191 | }; |
@@ -57,15 +57,18 b' var ajaxGET = function (url, success, fa' | |||
|
57 | 57 | return request; |
|
58 | 58 | }; |
|
59 | 59 | |
|
60 | var ajaxPOST = function (url, postData, success, failure) { | |
|
61 | var sUrl = url; | |
|
62 | var postData = toQueryString(postData); | |
|
63 | var request = $.ajax({ | |
|
60 | var ajaxPOST = function (url, postData, success, failure, options) { | |
|
61 | ||
|
62 | var ajaxSettings = $.extend({ | |
|
64 | 63 | type: 'POST', |
|
65 |
url: |
|
|
66 | data: postData, | |
|
64 | url: url, | |
|
65 | data: toQueryString(postData), | |
|
67 | 66 | headers: {'X-PARTIAL-XHR': true} |
|
68 | }) | |
|
67 | }, options); | |
|
68 | ||
|
69 | var request = $.ajax( | |
|
70 | ajaxSettings | |
|
71 | ) | |
|
69 | 72 | .done(function (data) { |
|
70 | 73 | success(data); |
|
71 | 74 | }) |
@@ -126,7 +129,8 b' function formatErrorMessage(jqXHR, textS' | |||
|
126 | 129 | } else if (errorThrown === 'abort') { |
|
127 | 130 | return (prefix + 'Ajax request aborted.'); |
|
128 | 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 |
@@ -33,7 +33,7 b'' | |||
|
33 | 33 | <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3> |
|
34 | 34 | </div> |
|
35 | 35 | <div class="panel-body panel-body-min-height"> |
|
36 |
<table id="pull_request_list_table" class=" |
|
|
36 | <table id="pull_request_list_table" class="rctable table-bordered"></table> | |
|
37 | 37 | </div> |
|
38 | 38 | </div> |
|
39 | 39 | |
@@ -58,16 +58,10 b'' | |||
|
58 | 58 | |
|
59 | 59 | dom: 'rtp', |
|
60 | 60 | pageLength: ${c.visual.dashboard_items}, |
|
61 |
order: [[ |
|
|
61 | order: [[1, "desc"]], | |
|
62 | 62 | columns: [ |
|
63 | 63 | { |
|
64 | 64 | data: { |
|
65 | "_": "target_repo", | |
|
66 | "sort": "target_repo" | |
|
67 | }, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false | |
|
68 | }, | |
|
69 | { | |
|
70 | data: { | |
|
71 | 65 | "_": "status", |
|
72 | 66 | "sort": "status" |
|
73 | 67 | }, title: "", className: "td-status", orderable: false |
@@ -101,7 +95,13 b'' | |||
|
101 | 95 | "_": "updated_on", |
|
102 | 96 | "sort": "updated_on_raw" |
|
103 | 97 | }, title: "${_('Last Update')}", className: "td-time" |
|
104 | } | |
|
98 | }, | |
|
99 | { | |
|
100 | data: { | |
|
101 | "_": "target_repo", | |
|
102 | "sort": "target_repo" | |
|
103 | }, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false | |
|
104 | }, | |
|
105 | 105 | ], |
|
106 | 106 | language: { |
|
107 | 107 | paginate: DEFAULT_GRID_PAGINATION, |
@@ -785,15 +785,15 b'' | |||
|
785 | 785 | |
|
786 | 786 | - Prefix query to allow special search: |
|
787 | 787 | |
|
788 | user:admin, to search for usernames, always global | |
|
788 | <strong>user:</strong>admin, to search for usernames, always global | |
|
789 | 789 | |
|
790 | user_group:devops, to search for user groups, always global | |
|
790 | <strong>user_group:</strong>devops, to search for user groups, always global | |
|
791 | 791 | |
|
792 | pr:303, to search for pull request number, title, or description, always global | |
|
792 | <strong>pr:</strong>303, to search for pull request number, title, or description, always global | |
|
793 | 793 | |
|
794 | commit:efced4, to search for commits, scoped to repositories or groups | |
|
794 | <strong>commit:</strong>efced4, to search for commits, scoped to repositories or groups | |
|
795 | 795 | |
|
796 | file:models.py, to search for file paths, scoped to repositories or groups | |
|
796 | <strong>file:</strong>models.py, to search for file paths, scoped to repositories or groups | |
|
797 | 797 | |
|
798 | 798 | % if c.template_context['search_context']['repo_id']: |
|
799 | 799 | For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a> |
@@ -16,8 +16,8 b' examples = [' | |||
|
16 | 16 | ), |
|
17 | 17 | |
|
18 | 18 | ( |
|
19 | 'Redmine', | |
|
20 |
'( |
|
|
19 | 'Tickets with #123 (Redmine etc)', | |
|
20 | '(?<![a-zA-Z0-9_/]{1,10}-?)(#)(?P<issue_id>\d+)', | |
|
21 | 21 | 'https://myissueserver.com/${repo}/issue/${issue_id}', |
|
22 | 22 | '' |
|
23 | 23 | ), |
@@ -38,14 +38,15 b' examples = [' | |||
|
38 | 38 | |
|
39 | 39 | ( |
|
40 | 40 | 'JIRA - All tickets', |
|
41 | '(^|\s\w+-\d+)', | |
|
42 | 'https://myjira.com/browse/${id}', | |
|
41 | # official JIRA ticket pattern | |
|
42 | '(?<![a-zA-Z0-9_/#]-?)(?P<issue_id>[A-Z]{1,6}-(?:[1-9][0-9]{0,7}))', | |
|
43 | 'https://myjira.com/browse/${issue_id}', | |
|
43 | 44 | '' |
|
44 | 45 | ), |
|
45 | 46 | |
|
46 | 47 | ( |
|
47 |
'JIRA - |
|
|
48 | '(?:(^|\s)(?P<issue_id>(?:JRA-|JRA-)(?:\d+)))', | |
|
48 | 'JIRA - Single project (JRA-XXXXXXXX)', | |
|
49 | '(?<![a-zA-Z0-9_/#]-?)(?P<issue_id>JRA-(?:[1-9][0-9]{0,7}))', | |
|
49 | 50 | 'https://myjira.com/${issue_id}', |
|
50 | 51 | '' |
|
51 | 52 | ), |
@@ -275,12 +276,18 b' examples = [' | |||
|
275 | 276 | <div class='textarea-full'> |
|
276 | 277 | <textarea id="test_pattern_data" rows="12"> |
|
277 | 278 | This is an example text for testing issue tracker patterns. |
|
278 | This commit fixes ticket #451 and ticket #910. | |
|
279 |
|
|
|
279 | This commit fixes ticket #451 and ticket #910, reference for JRA-401. | |
|
280 | The following tickets will get mentioned: | |
|
280 | 281 | #123 |
|
281 | #456 | |
|
282 | JRA-123 | |
|
283 |
J |
|
|
282 | #456 and PROJ-101 | |
|
283 | JRA-123 and #123 | |
|
284 | PROJ-456 | |
|
285 | ||
|
286 | [my artifact](http://something.com/JRA-1234-build.zip) | |
|
287 | ||
|
288 | - #1001 | |
|
289 | - JRA-998 | |
|
290 | ||
|
284 | 291 |
Open a pull request !101 to contribute |
|
285 | 292 | Added tag v1.3.0 for commit 0f3b629be725 |
|
286 | 293 |
@@ -89,36 +89,41 b'' | |||
|
89 | 89 | if is_pr: |
|
90 | 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 | ||
|
93 | <script type="text/javascript"> | |
|
94 | // closure function helper | |
|
95 | var sidebarComment${comment_obj.comment_id} = function() { | |
|
96 | return renderTemplate('sideBarCommentHovercard', { | |
|
97 | version_info: "${version_info}", | |
|
98 | file_name: "${comment_obj.f_path}", | |
|
99 | line_no: "${comment_obj.line_no}", | |
|
100 | outdated: ${h.json.dumps(comment_obj.outdated)}, | |
|
101 | inline: ${h.json.dumps(comment_obj.is_inline)}, | |
|
102 | is_todo: ${h.json.dumps(comment_obj.is_todo)}, | |
|
103 | created_on: "${h.format_date(comment_obj.created_on)}", | |
|
104 | datetime: "${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}", | |
|
105 | review_status: "${(comment_obj.review_status or '')}" | |
|
106 | }) | |
|
107 | } | |
|
108 | </script> | |
|
109 | ||
|
110 | % if comment_obj.outdated: | |
|
111 | <i class="icon-comment-toggle tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
112 | % elif comment_obj.is_inline: | |
|
113 | <i class="icon-code tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
114 | % else: | |
|
115 | <i class="icon-comment tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i> | |
|
92 | ## new comments, since refresh | |
|
93 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |
|
94 | <div class="tooltip" style="position: absolute; left: 8px; color: #682668" title="New comment"> | |
|
95 | ! | |
|
96 | </div> | |
|
116 | 97 | % endif |
|
117 | 98 |
|
|
118 |
|
|
|
119 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |
|
120 | <span class="tag">NEW</span> | |
|
121 | % endif | |
|
99 | <% | |
|
100 | data = h.json.dumps({ | |
|
101 | 'comment_id': comment_obj.comment_id, | |
|
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 | 127 | </td> |
|
123 | 128 | |
|
124 | 129 | <td class="td-todo-gravatar"> |
@@ -30,11 +30,21 b'' | |||
|
30 | 30 | </ul> |
|
31 | 31 | %endif |
|
32 | 32 | %if c.has_references: |
|
33 | <div class="grid-quick-filter"> | |
|
34 | <ul class="grid-filter-box"> | |
|
35 | <li class="grid-filter-box-icon"> | |
|
36 | <i class="icon-search"></i> | |
|
37 | </li> | |
|
38 | <li class="grid-filter-box-input"> | |
|
33 | 39 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> |
|
34 | <span id="obj_count">0</span> ${_('bookmarks')} | |
|
40 | </li> | |
|
41 | </ul> | |
|
42 | </div> | |
|
43 | <div id="obj_count">0</div> | |
|
35 | 44 | %endif |
|
36 | 45 | </div> |
|
37 | <table id="obj_list_table" class="display"></table> | |
|
46 | ||
|
47 | <table id="obj_list_table" class="rctable table-bordered"></table> | |
|
38 | 48 | </div> |
|
39 | 49 | |
|
40 | 50 | |
@@ -43,7 +53,9 b'' | |||
|
43 | 53 | |
|
44 | 54 | var get_datatable_count = function(){ |
|
45 | 55 | var api = $('#obj_list_table').dataTable().api(); |
|
46 |
|
|
|
56 | var total = api.page.info().recordsDisplay | |
|
57 | var _text = _ngettext('{0} bookmark', '{0} bookmarks', total).format(total); | |
|
58 | $('#obj_count').text(_text); | |
|
47 | 59 | }; |
|
48 | 60 | |
|
49 | 61 | // object list |
@@ -30,11 +30,20 b'' | |||
|
30 | 30 | </ul> |
|
31 | 31 | %endif |
|
32 | 32 | %if c.has_references: |
|
33 | <div class="grid-quick-filter"> | |
|
34 | <ul class="grid-filter-box"> | |
|
35 | <li class="grid-filter-box-icon"> | |
|
36 | <i class="icon-search"></i> | |
|
37 | </li> | |
|
38 | <li class="grid-filter-box-input"> | |
|
33 | 39 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> |
|
34 | <span id="obj_count">0</span> ${_('branches')} | |
|
40 | </li> | |
|
41 | </ul> | |
|
42 | </div> | |
|
43 | <div id="obj_count">0</div> | |
|
35 | 44 | %endif |
|
36 | 45 | </div> |
|
37 |
<table id="obj_list_table" class=" |
|
|
46 | <table id="obj_list_table" class="rctable table-bordered"></table> | |
|
38 | 47 | </div> |
|
39 | 48 | |
|
40 | 49 | <script type="text/javascript"> |
@@ -42,7 +51,10 b'' | |||
|
42 | 51 | |
|
43 | 52 | var get_datatable_count = function(){ |
|
44 | 53 | var api = $('#obj_list_table').dataTable().api(); |
|
45 |
|
|
|
54 | var total = api.page.info().recordsDisplay | |
|
55 | var _text = _ngettext('{0} branch', '{0} branches', total).format(total); | |
|
56 | ||
|
57 | $('#obj_count').text(_text); | |
|
46 | 58 | }; |
|
47 | 59 | |
|
48 | 60 | // object list |
@@ -187,12 +187,12 b'' | |||
|
187 | 187 | <div class="sidebar-element clear-both"> |
|
188 | 188 | <% vote_title = _ungettext( |
|
189 | 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 | 193 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> |
|
194 | 194 | <i class="icon-circle review-status-${c.commit_review_status}"></i> |
|
195 |
${ |
|
|
195 | ${c.reviewers_count} | |
|
196 | 196 | </div> |
|
197 | 197 | </div> |
|
198 | 198 | |
@@ -420,7 +420,8 b'' | |||
|
420 | 420 | e.preventDefault(); |
|
421 | 421 | }); |
|
422 | 422 | |
|
423 | ReviewersPanel.init(null, setReviewersData); | |
|
423 | reviewersController = new ReviewersController(); | |
|
424 | ReviewersPanel.init(reviewersController, null, setReviewersData); | |
|
424 | 425 | |
|
425 | 426 | var channel = '${c.commit_broadcast_channel}'; |
|
426 | 427 | new ReviewerPresenceController(channel) |
@@ -99,7 +99,7 b'' | |||
|
99 | 99 | <div id="graph_content" class="graph_full_width"> |
|
100 | 100 | |
|
101 | 101 | <div class="table"> |
|
102 | <table id="changesets" class="rctable"> | |
|
102 | <table id="changesets" class="rctable table-bordered"> | |
|
103 | 103 | <tr> |
|
104 | 104 | ## checkbox |
|
105 | 105 | <th colspan="4"> |
@@ -379,14 +379,15 b'' | |||
|
379 | 379 | </%def> |
|
380 | 380 | |
|
381 | 381 | <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)"> |
|
382 | <code> | |
|
382 | 383 | <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}"> |
|
383 | ||
|
384 | 384 | % if short: |
|
385 | 385 | !${pull_request_id} |
|
386 | 386 | % else: |
|
387 | 387 | ${_('Pull request !{}').format(pull_request_id)} |
|
388 | 388 | % endif |
|
389 | ||
|
389 | </a> | |
|
390 | </code> | |
|
390 | 391 |
|
|
391 | 392 |
|
|
392 | 393 |
|
@@ -394,7 +395,6 b'' | |||
|
394 | 395 |
|
|
395 | 396 |
|
|
396 | 397 |
|
|
397 | </a> | |
|
398 | 398 | </%def> |
|
399 | 399 | |
|
400 | 400 | <%def name="pullrequest_updated_on(updated_on)"> |
@@ -149,7 +149,7 b'' | |||
|
149 | 149 | <span class="user"> <a href="/_profiles/jenkins-tests">jenkins-tests</a> (reviewer)</span> |
|
150 | 150 | </div> |
|
151 | 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 | 153 | <i class="icon-remove"></i> |
|
154 | 154 | </div> |
|
155 | 155 | </li> |
@@ -66,9 +66,18 b" var data_hovercard_url = pyroutes.url('h" | |||
|
66 | 66 | <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>"> |
|
67 | 67 | |
|
68 | 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 | 73 | <div class="reviewer_status tooltip" title="<%= review_status_label %>"> |
|
70 | 74 | <i class="icon-circle review-status-<%= review_status %>"></i> |
|
71 | 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 |
|
|
73 | 82 | |
|
74 | 83 | <td> |
@@ -84,9 +93,6 b" var data_hovercard_url = pyroutes.url('h" | |||
|
84 | 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 |
|
|
91 | 97 | </td> |
|
92 | 98 | |
@@ -108,7 +114,7 b" var data_hovercard_url = pyroutes.url('h" | |||
|
108 | 114 | <% } else { %> |
|
109 | 115 | <td style="text-align: right;width: 10px;"> |
|
110 | 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 | 118 | <i class="icon-remove"></i> |
|
113 | 119 | </div> |
|
114 | 120 | <% } %> |
@@ -117,7 +123,7 b" var data_hovercard_url = pyroutes.url('h" | |||
|
117 | 123 | |
|
118 | 124 | </tr> |
|
119 | 125 | |
|
120 | <tr> | |
|
126 | <tr id="reviewer_<%= member.user_id %>_rules"> | |
|
121 | 127 | <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container"> |
|
122 | 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 | 156 | <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" /> |
|
151 | 157 | <input type="hidden" name="mandatory" value="<%= mandatory %>"/> |
|
158 | <input type="hidden" name="role" value="<%= role %>"/> | |
|
152 | 159 | |
|
153 | 160 | <input type="hidden" name="__end__" value="reviewer:mapping"> |
|
154 | 161 | </td> |
@@ -11,6 +11,9 b' data = {' | |||
|
11 | 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 | 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 | 37 | 'source_repo_url': pull_request_source_repo_url, |
|
35 | 38 | 'target_repo_url': pull_request_target_repo_url, |
|
36 | 39 | } |
|
40 | ||
|
37 | 41 | %> |
|
38 | 42 | |
|
39 | 43 | * ${_('Pull Request link')}: ${pull_request_url} |
@@ -51,7 +55,7 b' data = {' | |||
|
51 | 55 | |
|
52 | 56 | % for commit_id, message in pull_request_commits: |
|
53 | 57 | - ${h.short_id(commit_id)} |
|
54 |
|
|
|
58 | ${h.chop_at_smart(message.lstrip(), '\n', suffix_if_chopped='...')} | |
|
55 | 59 | |
|
56 | 60 | % endfor |
|
57 | 61 | |
@@ -78,19 +82,23 b' data = {' | |||
|
78 | 82 | <table style="text-align:left;vertical-align:middle;width: 100%"> |
|
79 | 83 | <tr> |
|
80 | 84 | <td style="width:100%;border-bottom:1px solid #dbd9da;"> |
|
81 | ||
|
82 | 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 | 93 | <div class="clear-both" class="clear-both" style="margin-bottom: 4px"> |
|
84 | 94 | <span style="color:#7E7F7F">@${h.person(user.username)}</span> |
|
85 | 95 | ${_('requested a')} |
|
86 | <a href="${pull_request_url}" style="${base.link_css()}"> | |
|
87 | ${_('pull request review.').format(**data) } | |
|
88 | </a> | |
|
96 | <a href="${pull_request_url}" style="${base.link_css()}">pull request</a> review. | |
|
89 | 97 | </div> |
|
98 | % endif | |
|
90 | 99 | <div style="margin-top: 10px"></div> |
|
91 | 100 | ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code> |
|
92 | 101 | </div> |
|
93 | ||
|
94 | 102 | </td> |
|
95 | 103 | </tr> |
|
96 | 104 |
@@ -10,7 +10,7 b'' | |||
|
10 | 10 | default_landing_ref = c.commit.raw_id |
|
11 | 11 | %> |
|
12 | 12 | <div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}"> |
|
13 |
<table class="code-browser rctable |
|
|
13 | <table class="code-browser rctable table-bordered"> | |
|
14 | 14 | <thead> |
|
15 | 15 | <tr> |
|
16 | 16 | <th>${_('Name')}</th> |
@@ -112,7 +112,7 b'' | |||
|
112 | 112 | ## REVIEWERS |
|
113 | 113 | <div class="field"> |
|
114 | 114 | <div class="label label-textarea"> |
|
115 | <label for="pullrequest_reviewers">${_('Reviewers')}:</label> | |
|
115 | <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label> | |
|
116 | 116 | </div> |
|
117 | 117 | <div class="content"> |
|
118 | 118 | ## REVIEW RULES |
@@ -125,18 +125,40 b'' | |||
|
125 | 125 | </div> |
|
126 | 126 | </div> |
|
127 | 127 | |
|
128 | ## REVIEWERS | |
|
128 | ## REVIEWERS / OBSERVERS | |
|
129 | 129 | <div class="reviewers-title"> |
|
130 | <div class="pr-details-title"> | |
|
131 |
|
|
|
132 | <span class="calculate-reviewers"> - ${_('loading...')}</span> | |
|
133 |
|
|
|
134 |
< |
|
|
130 | ||
|
131 | <ul class="nav-links clearfix"> | |
|
132 | ||
|
133 | ## TAB1 MANDATORY REVIEWERS | |
|
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 | 157 | <div id="reviewers" class="pr-details-content reviewers"> |
|
136 | 158 | ## members goes here, filled via JS based on initial selection ! |
|
137 | 159 | <input type="hidden" name="__start__" value="review_members:sequence"> |
|
138 | 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 | 162 | </table> |
|
141 | 163 | <input type="hidden" name="__end__" value="review_members:sequence"> |
|
142 | 164 | |
@@ -149,6 +171,39 b'' | |||
|
149 | 171 | |
|
150 | 172 | </div> |
|
151 | 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 | % if c.rhodecode_edition_id == 'EE': | |
|
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 | % else: | |
|
197 | <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4> | |
|
198 | <p> | |
|
199 | Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes. | |
|
200 | </p> | |
|
201 | % endif | |
|
202 | </div> | |
|
203 | ||
|
204 | </div> | |
|
205 | ||
|
206 | </div> | |
|
152 | 207 | </div> |
|
153 | 208 | |
|
154 | 209 | ## SUBMIT |
@@ -248,12 +303,19 b'' | |||
|
248 | 303 | |
|
249 | 304 | var originalOption = data.element; |
|
250 | 305 | return prefix + escapeMarkup(data.text); |
|
251 | };formatSelection: | |
|
306 | }; | |
|
252 | 307 | |
|
253 | 308 | // custom code mirror |
|
254 | 309 | var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm; |
|
255 | 310 | |
|
256 | 311 | var diffDataHandler = function(data) { |
|
312 | if (data['error'] !== undefined) { | |
|
313 | var noCommitsMsg = '<span class="alert-text-error">{0}</span>'.format(data['error']); | |
|
314 | prButtonLock(true, noCommitsMsg, 'compare'); | |
|
315 | //make both panels equal | |
|
316 | $('.target-panel').height($('.source-panel').height()) | |
|
317 | return false | |
|
318 | } | |
|
257 | 319 | |
|
258 | 320 | var commitElements = data['commits']; |
|
259 | 321 | var files = data['files']; |
@@ -307,8 +369,10 b'' | |||
|
307 | 369 | var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId); |
|
308 | 370 | msg += '<input type="hidden" name="__start__" value="revisions:sequence">' |
|
309 | 371 | |
|
372 | ||
|
310 | 373 | $.each(commitElements, function(idx, value) { |
|
311 | msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]); | |
|
374 | var commit_id = value["commit_id"] | |
|
375 | msg += '<input type="hidden" name="revisions" value="{0}">'.format(commit_id); | |
|
312 | 376 | }); |
|
313 | 377 | |
|
314 | 378 | msg += '<input type="hidden" name="__end__" value="revisions:sequence">' |
@@ -338,8 +402,8 b'' | |||
|
338 | 402 | } |
|
339 | 403 | |
|
340 | 404 | //make both panels equal |
|
341 | $('.target-panel').height($('.source-panel').height()) | |
|
342 | ||
|
405 | $('.target-panel').height($('.source-panel').height()); | |
|
406 | return true | |
|
343 | 407 | }; |
|
344 | 408 | |
|
345 | 409 | reviewersController = new ReviewersController(); |
@@ -465,8 +529,7 b'' | |||
|
465 | 529 | queryTargetRefs(initialData, query) |
|
466 | 530 | }, |
|
467 | 531 | initSelection: initRefSelection() |
|
468 |
|
|
|
469 | ); | |
|
532 | }); | |
|
470 | 533 | |
|
471 | 534 | var sourceRepoSelect2 = Select2Box($sourceRepo, { |
|
472 | 535 | query: function(query) {} |
@@ -543,12 +606,44 b'' | |||
|
543 | 606 | $sourceRef.select2('val', '${c.default_source_ref}'); |
|
544 | 607 | |
|
545 | 608 | |
|
546 | // default reviewers | |
|
609 | // default reviewers / observers | |
|
547 | 610 | reviewersController.loadDefaultReviewers( |
|
548 | 611 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
549 | 612 | % endif |
|
550 | 613 | |
|
551 | ReviewerAutoComplete('#user'); | |
|
614 | ReviewerAutoComplete('#user', reviewersController); | |
|
615 | ObserverAutoComplete('#observer', reviewersController); | |
|
616 | ||
|
617 | // TODO, move this to another handler | |
|
618 | ||
|
619 | var $reviewersBtn = $('#reviewers-btn'); | |
|
620 | var $reviewersContainer = $('#reviewers-container'); | |
|
621 | ||
|
622 | var $observersBtn = $('#observers-btn') | |
|
623 | var $observersContainer = $('#observers-container'); | |
|
624 | ||
|
625 | $reviewersBtn.on('click', function (e) { | |
|
626 | ||
|
627 | $observersContainer.hide(); | |
|
628 | $reviewersContainer.show(); | |
|
629 | ||
|
630 | $observersBtn.parent().removeClass('active'); | |
|
631 | $reviewersBtn.parent().addClass('active'); | |
|
632 | e.preventDefault(); | |
|
633 | ||
|
634 | }) | |
|
635 | ||
|
636 | $observersBtn.on('click', function (e) { | |
|
637 | ||
|
638 | $reviewersContainer.hide(); | |
|
639 | $observersContainer.show(); | |
|
640 | ||
|
641 | $reviewersBtn.parent().removeClass('active'); | |
|
642 | $observersBtn.parent().addClass('active'); | |
|
643 | e.preventDefault(); | |
|
644 | ||
|
645 | }) | |
|
646 | ||
|
552 | 647 | }); |
|
553 | 648 | </script> |
|
554 | 649 |
@@ -556,12 +556,12 b'' | |||
|
556 | 556 | <div class="sidebar-element clear-both"> |
|
557 | 557 | <% vote_title = _ungettext( |
|
558 | 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 | 562 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> |
|
563 | 563 | <i class="icon-circle review-status-${c.pull_request_review_status}"></i> |
|
564 |
${ |
|
|
564 | ${c.reviewers_count} | |
|
565 | 565 | </div> |
|
566 | 566 | |
|
567 | 567 | ## REVIEW RULES |
@@ -609,13 +609,13 b'' | |||
|
609 | 609 | <div id="add_reviewer" class="ac" style="display: none;"> |
|
610 | 610 | %if c.allowed_to_update: |
|
611 | 611 | % if not c.forbid_adding_reviewers: |
|
612 | <div id="add_reviewer_input" class="reviewer_ac"> | |
|
613 |
|
|
|
612 | <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px"> | |
|
613 | <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off"> | |
|
614 | 614 | <div id="reviewers_container"></div> |
|
615 | 615 | </div> |
|
616 | 616 | % endif |
|
617 | <div class="pull-right"> | |
|
618 |
<button id="update_ |
|
|
617 | <div class="pull-right" style="margin-bottom: 15px"> | |
|
618 | <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button> | |
|
619 | 619 | </div> |
|
620 | 620 | %endif |
|
621 | 621 | </div> |
@@ -623,23 +623,59 b'' | |||
|
623 | 623 | </div> |
|
624 | 624 | </div> |
|
625 | 625 | |
|
626 |
|
|
|
627 | ## <div class="sidebar-element clear-both"> | |
|
628 | ## <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}"> | |
|
629 | ## <i class="icon-eye"></i> | |
|
630 | ## 0 | |
|
631 | ## </div> | |
|
632 | ## | |
|
633 | ## <div class="right-sidebar-expanded-state pr-details-title"> | |
|
634 | ## <span class="sidebar-heading"> | |
|
635 |
|
|
|
636 |
|
|
|
637 |
|
|
|
638 | ## </div> | |
|
639 |
|
|
|
640 | ## No observers | |
|
641 | ## </div> | |
|
642 | ## </div> | |
|
626 | ## OBSERVERS | |
|
627 | % if c.rhodecode_edition_id == 'EE': | |
|
628 | <div class="sidebar-element clear-both"> | |
|
629 | <% vote_title = _ungettext( | |
|
630 | '{} observer without voting right.', | |
|
631 | '{} observers without voting right.', c.observers_count).format(c.observers_count) | |
|
632 | %> | |
|
633 | ||
|
634 | <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}"> | |
|
635 | <i class="icon-circle-thin"></i> | |
|
636 | ${c.observers_count} | |
|
637 | </div> | |
|
638 | ||
|
639 | <div class="right-sidebar-expanded-state pr-details-title"> | |
|
640 | <span class="tooltip sidebar-heading" title="${vote_title}"> | |
|
641 | <i class="icon-circle-thin"></i> | |
|
642 | ${_('Observers')} | |
|
643 | </span> | |
|
644 | %if c.allowed_to_update: | |
|
645 | <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span> | |
|
646 | <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span> | |
|
647 | %endif | |
|
648 | </div> | |
|
649 | ||
|
650 | <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers"> | |
|
651 | ## members redering block | |
|
652 | <input type="hidden" name="__start__" value="observer_members:sequence"> | |
|
653 | ||
|
654 | <table id="observer_members" class="group_members"> | |
|
655 | ## This content is loaded via JS and ReviewersPanel | |
|
656 | </table> | |
|
657 | ||
|
658 | <input type="hidden" name="__end__" value="observer_members:sequence"> | |
|
659 | ## end members redering block | |
|
660 | ||
|
661 | %if not c.pull_request.is_closed(): | |
|
662 | <div id="add_observer" class="ac" style="display: none;"> | |
|
663 | %if c.allowed_to_update: | |
|
664 | % if not c.forbid_adding_reviewers or 1: | |
|
665 | <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" > | |
|
666 | <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off"> | |
|
667 | <div id="observers_container"></div> | |
|
668 | </div> | |
|
669 | % endif | |
|
670 | <div class="pull-right" style="margin-bottom: 15px"> | |
|
671 | <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button> | |
|
672 | </div> | |
|
673 | %endif | |
|
674 | </div> | |
|
675 | %endif | |
|
676 | </div> | |
|
677 | </div> | |
|
678 | % endif | |
|
643 | 679 | |
|
644 | 680 | ## TODOs |
|
645 | 681 | <div class="sidebar-element clear-both"> |
@@ -757,7 +793,7 b'' | |||
|
757 | 793 | |
|
758 | 794 | <tr><td><code>${_('In pull request description')}:</code></td></tr> |
|
759 | 795 | % if c.referenced_desc_issues: |
|
760 | % for ticket_dict in c.referenced_desc_issues: | |
|
796 | % for ticket_dict in sorted(c.referenced_desc_issues): | |
|
761 | 797 | <tr> |
|
762 | 798 | <td> |
|
763 | 799 | <a href="${ticket_dict.get('url')}"> |
@@ -776,7 +812,7 b'' | |||
|
776 | 812 | |
|
777 | 813 | <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr> |
|
778 | 814 | % if c.referenced_commit_issues: |
|
779 | % for ticket_dict in c.referenced_commit_issues: | |
|
815 | % for ticket_dict in sorted(c.referenced_commit_issues): | |
|
780 | 816 | <tr> |
|
781 | 817 | <td> |
|
782 | 818 | <a href="${ticket_dict.get('url')}"> |
@@ -815,6 +851,7 b' updateController = new UpdatePrControlle' | |||
|
815 | 851 | |
|
816 | 852 | window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n}; |
|
817 | 853 | window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n}; |
|
854 | window.setObserversData = ${c.pull_request_set_observers_data_json | n}; | |
|
818 | 855 | |
|
819 | 856 | (function () { |
|
820 | 857 | "use strict"; |
@@ -822,44 +859,9 b' window.setReviewersData = ${c.pull_reque' | |||
|
822 | 859 | // custom code mirror |
|
823 | 860 | var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm; |
|
824 | 861 | |
|
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 | 862 | PRDetails.init(); |
|
862 | ReviewersPanel.init(reviewerRulesData, setReviewersData); | |
|
863 | ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData); | |
|
864 | ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData); | |
|
863 | 865 | |
|
864 | 866 | window.showOutdated = function (self) { |
|
865 | 867 | $('.comment-inline.comment-outdated').show(); |
@@ -929,12 +931,17 b' window.setReviewersData = ${c.pull_reque' | |||
|
929 | 931 | title, description, renderer); |
|
930 | 932 | }); |
|
931 | 933 | |
|
932 | $('#update_pull_request').on('click', function (e) { | |
|
933 | $(this).attr('disabled', 'disabled'); | |
|
934 |
$(this). |
|
|
935 | $(this).html(_gettext('Saving...')); | |
|
934 | var $updateButtons = $('#update_reviewers,#update_observers'); | |
|
935 | $updateButtons.on('click', function (e) { | |
|
936 | var role = $(this).data('role'); | |
|
937 | $updateButtons.attr('disabled', 'disabled'); | |
|
938 | $updateButtons.addClass('disabled'); | |
|
939 | $updateButtons.html(_gettext('Saving...')); | |
|
936 | 940 | reviewersController.updateReviewers( |
|
937 | "${c.repo_name}", "${c.pull_request.pull_request_id}"); | |
|
941 | templateContext.repo_name, | |
|
942 | templateContext.pull_request_data.pull_request_id, | |
|
943 | role | |
|
944 | ); | |
|
938 | 945 | }); |
|
939 | 946 | |
|
940 | 947 | // fixing issue with caches on firefox |
@@ -978,7 +985,8 b' window.setReviewersData = ${c.pull_reque' | |||
|
978 | 985 | refreshMergeChecks(); |
|
979 | 986 | }; |
|
980 | 987 | |
|
981 | ReviewerAutoComplete('#user'); | |
|
988 | ReviewerAutoComplete('#user', reviewersController); | |
|
989 | ObserverAutoComplete('#observer', reviewersController); | |
|
982 | 990 | |
|
983 | 991 | })(); |
|
984 | 992 |
@@ -61,7 +61,7 b'' | |||
|
61 | 61 | </div> |
|
62 | 62 | |
|
63 | 63 | <div class="main-content-full-width"> |
|
64 |
<table id="pull_request_list_table" class=" |
|
|
64 | <table id="pull_request_list_table" class="rctable table-bordered"></table> | |
|
65 | 65 | </div> |
|
66 | 66 | |
|
67 | 67 | </div> |
@@ -1,11 +1,11 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | %if c.repo_commits: |
|
4 |
<table class="rctable |
|
|
4 | <table class="rctable table-bordered"> | |
|
5 | 5 | <tr> |
|
6 | 6 | |
|
7 | 7 | <th class="status"></th> |
|
8 |
<th> |
|
|
8 | <th></th> | |
|
9 | 9 | <th>${_('Commit message')}</th> |
|
10 | 10 | <th>${_('Age')}</th> |
|
11 | 11 | <th>${_('Author')}</th> |
@@ -30,11 +30,20 b'' | |||
|
30 | 30 | </ul> |
|
31 | 31 | %endif |
|
32 | 32 | %if c.has_references: |
|
33 | <div class="grid-quick-filter"> | |
|
34 | <ul class="grid-filter-box"> | |
|
35 | <li class="grid-filter-box-icon"> | |
|
36 | <i class="icon-search"></i> | |
|
37 | </li> | |
|
38 | <li class="grid-filter-box-input"> | |
|
33 | 39 | <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/> |
|
34 | <span id="obj_count">0</span> ${_('tags')} | |
|
40 | </li> | |
|
41 | </ul> | |
|
42 | </div> | |
|
43 | <div id="obj_count">0</div> | |
|
35 | 44 | %endif |
|
36 | 45 | </div> |
|
37 |
<table id="obj_list_table" class=" |
|
|
46 | <table id="obj_list_table" class="rctable table-bordered"></table> | |
|
38 | 47 | </div> |
|
39 | 48 | |
|
40 | 49 | |
@@ -43,7 +52,10 b'' | |||
|
43 | 52 | |
|
44 | 53 | var get_datatable_count = function(){ |
|
45 | 54 | var api = $('#obj_list_table').dataTable().api(); |
|
46 |
|
|
|
55 | var total = api.page.info().recordsDisplay | |
|
56 | var _text = _ngettext('{0} tag', '{0} tags', total).format(total); | |
|
57 | ||
|
58 | $('#obj_count').text(_text); | |
|
47 | 59 | }; |
|
48 | 60 | |
|
49 | 61 | // object list |
@@ -121,7 +121,7 b' def test_extract_issues(backend, text_st' | |||
|
121 | 121 | |
|
122 | 122 | with mock.patch.object(IssueTrackerSettingsModel, |
|
123 | 123 | 'get_settings', get_settings_mock): |
|
124 | text, issues = helpers.process_patterns(text_string, repo.repo_name) | |
|
124 | text, issues, errors = helpers.process_patterns(text_string, repo.repo_name) | |
|
125 | 125 | |
|
126 | 126 | expected = copy.deepcopy(expected) |
|
127 | 127 | for item in expected: |
@@ -159,7 +159,7 b' def test_process_patterns_repo(backend, ' | |||
|
159 | 159 | |
|
160 | 160 | with mock.patch.object(IssueTrackerSettingsModel, |
|
161 | 161 | 'get_settings', get_settings_mock): |
|
162 | processed_text, issues = helpers.process_patterns( | |
|
162 | processed_text, issues, error = helpers.process_patterns( | |
|
163 | 163 | text_string, repo.repo_name, link_format) |
|
164 | 164 | |
|
165 | 165 | assert processed_text == expected_text.format(repo=repo.repo_name) |
@@ -186,7 +186,7 b' def test_process_patterns_no_repo(text_s' | |||
|
186 | 186 | |
|
187 | 187 | with mock.patch.object(IssueTrackerSettingsModel, |
|
188 | 188 | 'get_global_settings', get_settings_mock): |
|
189 | processed_text, issues = helpers.process_patterns( | |
|
189 | processed_text, issues, errors = helpers.process_patterns( | |
|
190 | 190 | text_string, '') |
|
191 | 191 | |
|
192 | 192 | assert processed_text == expected_text |
@@ -211,7 +211,7 b' def test_process_patterns_non_existent_r' | |||
|
211 | 211 | |
|
212 | 212 | with mock.patch.object(IssueTrackerSettingsModel, |
|
213 | 213 | 'get_global_settings', get_settings_mock): |
|
214 | processed_text, issues = helpers.process_patterns( | |
|
214 | processed_text, issues, errors = helpers.process_patterns( | |
|
215 | 215 | text_string, 'do-not-exist') |
|
216 | 216 | |
|
217 | 217 | assert processed_text == expected_text |
@@ -23,7 +23,7 b' import collections' | |||
|
23 | 23 | |
|
24 | 24 | from rhodecode.lib.partial_renderer import PyramidPartialRenderer |
|
25 | 25 | from rhodecode.lib.utils2 import AttributeDict |
|
26 | from rhodecode.model.db import User | |
|
26 | from rhodecode.model.db import User, PullRequestReviewers | |
|
27 | 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 | 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 | 57 | ref = collections.namedtuple( |
|
57 | 58 | 'Ref', 'name, type')('fxies123', 'book') |
|
58 | 59 | |
@@ -75,13 +76,17 b' def test_render_pr_email(app, user_admin' | |||
|
75 | 76 | 'pull_request_source_repo_url': 'x', |
|
76 | 77 | |
|
77 | 78 | 'pull_request_url': 'http://localhost/pr1', |
|
79 | 'user_role': role, | |
|
78 | 80 | } |
|
79 | 81 | |
|
80 | 82 | subject, body, body_plaintext = EmailNotificationModel().render_email( |
|
81 | 83 | EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs) |
|
82 | 84 | |
|
83 | 85 | # subject |
|
86 | if role == PullRequestReviewers.ROLE_REVIEWER: | |
|
84 | 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 | 92 | def test_render_pr_update_email(app, user_admin): |
@@ -122,7 +122,7 b' class TestPullRequestModel(object):' | |||
|
122 | 122 | |
|
123 | 123 | def test_get_awaiting_my_review(self, pull_request): |
|
124 | 124 | PullRequestModel().update_reviewers( |
|
125 | pull_request, [(pull_request.author, ['author'], False, [])], | |
|
125 | pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])], | |
|
126 | 126 | pull_request.author) |
|
127 | 127 | Session().commit() |
|
128 | 128 | |
@@ -133,7 +133,7 b' class TestPullRequestModel(object):' | |||
|
133 | 133 | |
|
134 | 134 | def test_count_awaiting_my_review(self, pull_request): |
|
135 | 135 | PullRequestModel().update_reviewers( |
|
136 | pull_request, [(pull_request.author, ['author'], False, [])], | |
|
136 | pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])], | |
|
137 | 137 | pull_request.author) |
|
138 | 138 | Session().commit() |
|
139 | 139 |
@@ -43,8 +43,8 b' from rhodecode.lib.utils2 import Attribu' | |||
|
43 | 43 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
44 | 44 | from rhodecode.model.comment import CommentsModel |
|
45 | 45 | from rhodecode.model.db import ( |
|
46 |
PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, |
|
|
47 | UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi) | |
|
46 | PullRequest, PullRequestReviewers, Repository, RhodeCodeSetting, ChangesetStatus, | |
|
47 | RepoGroup, UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi) | |
|
48 | 48 | from rhodecode.model.meta import Session |
|
49 | 49 | from rhodecode.model.pull_request import PullRequestModel |
|
50 | 50 | from rhodecode.model.repo import RepoModel |
@@ -968,7 +968,7 b' class PRTestUtility(object):' | |||
|
968 | 968 | def create_pull_request( |
|
969 | 969 | self, commits=None, target_head=None, source_head=None, |
|
970 | 970 | revisions=None, approved=False, author=None, mergeable=False, |
|
971 | enable_notifications=True, name_suffix=u'', reviewers=None, | |
|
971 | enable_notifications=True, name_suffix=u'', reviewers=None, observers=None, | |
|
972 | 972 | title=u"Test", description=u"Description"): |
|
973 | 973 | self.set_mergeable(mergeable) |
|
974 | 974 | if not enable_notifications: |
@@ -1005,6 +1005,7 b' class PRTestUtility(object):' | |||
|
1005 | 1005 | 'target_ref': self._default_branch_reference(target_head), |
|
1006 | 1006 | 'revisions': [self.commit_ids[r] for r in revisions], |
|
1007 | 1007 | 'reviewers': reviewers or self._get_reviewers(), |
|
1008 | 'observers': observers or self._get_observers(), | |
|
1008 | 1009 | 'title': title, |
|
1009 | 1010 | 'description': description, |
|
1010 | 1011 | } |
@@ -1037,9 +1038,15 b' class PRTestUtility(object):' | |||
|
1037 | 1038 | return reference |
|
1038 | 1039 | |
|
1039 | 1040 | def _get_reviewers(self): |
|
1041 | role = PullRequestReviewers.ROLE_REVIEWER | |
|
1040 | 1042 | return [ |
|
1041 | (TEST_USER_REGULAR_LOGIN, ['default1'], False, []), | |
|
1042 | (TEST_USER_REGULAR2_LOGIN, ['default2'], False, []), | |
|
1043 | (TEST_USER_REGULAR_LOGIN, ['default1'], False, role, []), | |
|
1044 | (TEST_USER_REGULAR2_LOGIN, ['default2'], False, role, []), | |
|
1045 | ] | |
|
1046 | ||
|
1047 | def _get_observers(self): | |
|
1048 | return [ | |
|
1049 | ||
|
1043 | 1050 | ] |
|
1044 | 1051 | |
|
1045 | 1052 | def update_source_repository(self, head=None): |
General Comments 0
You need to be logged in to leave comments.
Login now