release: merge back stable branch into default
This commit is contained in:
commit
0b0df5eb20
81 changed files with 3226 additions and 1639 deletions
|
|
@ -90,7 +90,7 @@ comment_pull_request
|
|||
create_pull_request
|
||||
-------------------
|
||||
|
||||
.. 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>)
|
||||
.. 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>)
|
||||
|
||||
Creates a new pull request.
|
||||
|
||||
|
|
@ -128,6 +128,13 @@ create_pull_request
|
|||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
|
||||
:param observers: Set the new pull request observers list.
|
||||
Reviewer defined by review rules will be added automatically to the
|
||||
defined list. This feature is only available in RhodeCode EE
|
||||
:type observers: Optional(list)
|
||||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author']}]
|
||||
|
||||
|
||||
get_pull_request
|
||||
|
|
@ -392,7 +399,7 @@ merge_pull_request
|
|||
update_pull_request
|
||||
-------------------
|
||||
|
||||
.. py:function:: update_pull_request(apiuser, pullrequestid, repoid=<Optional:None>, title=<Optional:''>, description=<Optional:''>, description_renderer=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>)
|
||||
.. 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>)
|
||||
|
||||
Updates a pull request.
|
||||
|
||||
|
|
@ -414,7 +421,11 @@ update_pull_request
|
|||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
|
||||
:param observers: Update pull request observers list with new value.
|
||||
:type observers: Optional(list)
|
||||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['should be aware about this PR']}]
|
||||
:param update_commits: Trigger update of commits for this pull request
|
||||
:type: update_commits: Optional(bool)
|
||||
|
||||
|
|
@ -432,6 +443,12 @@ update_pull_request
|
|||
],
|
||||
"removed": []
|
||||
},
|
||||
"updated_observers": {
|
||||
"added": [
|
||||
"username"
|
||||
],
|
||||
"removed": []
|
||||
},
|
||||
"updated_commits": {
|
||||
"added": [
|
||||
"<sha1_hash>"
|
||||
|
|
|
|||
54
docs/release-notes/release-notes-4.21.0.rst
Normal file
54
docs/release-notes/release-notes-4.21.0.rst
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
|RCE| 4.21.0 |RNS|
|
||||
------------------
|
||||
|
||||
Release Date
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- 2020-09-28
|
||||
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- Pull requests: overhaul of the UX/UI by adding new sidebar
|
||||
- Pull requests: new live reviewer present indicator (requires channelstream enabled)
|
||||
- Pull requests: new live new comments indicator (requires channelstream enabled)
|
||||
- Pull requests: new sidebar with comments/todos/referenced tickets navigation
|
||||
- Commits page: Introduced sidebar for single commits pages
|
||||
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
|
||||
- API: allow repo admins to get/set settings.
|
||||
Previously it was only super-admins that could do that.
|
||||
- Sessions: patch baker to take expire time for redis for auto session cleanup feature.
|
||||
- Git: bumped git version to 2.27.0
|
||||
- Packages: bumped to channelstream==0.6.14
|
||||
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
- Issue trackers: fix XSS with description field.
|
||||
|
||||
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Artifacts: speed-up of artifacts download request processing.
|
||||
|
||||
|
||||
Fixes
|
||||
^^^^^
|
||||
|
||||
- Pull requests: properly save merge failure metadata.
|
||||
In rare cases merge check reported conflicts which there were none.
|
||||
- Sessions: fixed cleanup with corrupted session data issue.
|
||||
|
||||
|
||||
Upgrade notes
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
- Scheduled feature release.
|
||||
- Git version was bumped to 2.27.0
|
||||
55
docs/release-notes/release-notes-4.22.0.rst
Normal file
55
docs/release-notes/release-notes-4.22.0.rst
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
|RCE| 4.22.0 |RNS|
|
||||
------------------
|
||||
|
||||
Release Date
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- 2020-10-12
|
||||
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- Reviewers: added observers as another role for reviewers.
|
||||
Observers is a role that doesn't require voting, but still gets notified about
|
||||
PR and should participate in review process.
|
||||
- Issue trackers: implemented more sophisticated ticket data extraction based on
|
||||
advanced regex module. This allows using ticket references without false positives
|
||||
like catching ticket data in an URL.
|
||||
- Channelstream: Notification about updates and comments now works via API, and both
|
||||
Pull-requests and individual commits.
|
||||
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
|
||||
- Data tables: unified tables look for main pages of rhodecode repo pages.
|
||||
- Users: autocomplete now sorts by matched username to show best matches first.
|
||||
- Pull requests: only allow actual reviewers to leave status/votes in order to not
|
||||
confuse others users about voting from people who aren't actual reviewers.
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
|
||||
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
- Default reviewers: optimize diff data, and creation of PR with advanced default reviewers
|
||||
- default-reviewers: diff data should load more things lazy for better performance.
|
||||
- Pull requests: limit the amount of data saved in default reviewers data for better memory usage
|
||||
- DB: don't use lazy loaders on PR related objects, to optimize memory usage on large
|
||||
Pull requests with lots of comments, and commits.
|
||||
|
||||
Fixes
|
||||
^^^^^
|
||||
|
||||
- Quick search bar: fixes #5634, crash when search on non-ascii characters.
|
||||
- Sidebar: few fixes for panel rendering of reviewers/observers for both commits and PRS.
|
||||
|
||||
Upgrade notes
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
- Scheduled feature release.
|
||||
|
||||
|
|
@ -9,6 +9,8 @@ Release Notes
|
|||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
release-notes-4.22.0.rst
|
||||
release-notes-4.21.0.rst
|
||||
release-notes-4.20.1.rst
|
||||
release-notes-4.20.0.rst
|
||||
release-notes-4.19.3.rst
|
||||
|
|
|
|||
|
|
@ -1816,6 +1816,17 @@ self: super: {
|
|||
license = [ pkgs.lib.licenses.mit ];
|
||||
};
|
||||
};
|
||||
"regex" = super.buildPythonPackage {
|
||||
name = "regex-2020.9.27";
|
||||
doCheck = false;
|
||||
src = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/93/8c/17f45cdfb39b13d4b5f909e4b4c2917abcbdef9c0036919a0399769148cf/regex-2020.9.27.tar.gz";
|
||||
sha256 = "179ngfzwbsjvn5vhyzdahvmg0f7acahkwwy9bpjy1pv08bm2mwx6";
|
||||
};
|
||||
meta = {
|
||||
license = [ pkgs.lib.licenses.psfl ];
|
||||
};
|
||||
};
|
||||
"redis" = super.buildPythonPackage {
|
||||
name = "redis-3.4.1";
|
||||
doCheck = false;
|
||||
|
|
@ -1872,7 +1883,7 @@ self: super: {
|
|||
};
|
||||
};
|
||||
"rhodecode-enterprise-ce" = super.buildPythonPackage {
|
||||
name = "rhodecode-enterprise-ce-4.20.0";
|
||||
name = "rhodecode-enterprise-ce-4.22.0";
|
||||
buildInputs = [
|
||||
self."pytest"
|
||||
self."py"
|
||||
|
|
@ -1946,6 +1957,7 @@ self: super: {
|
|||
self."tzlocal"
|
||||
self."pyzmq"
|
||||
self."py-gfm"
|
||||
self."regex"
|
||||
self."redis"
|
||||
self."repoze.lru"
|
||||
self."requests"
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ pytz==2019.3
|
|||
tzlocal==1.5.1
|
||||
pyzmq==14.6.0
|
||||
py-gfm==0.1.4
|
||||
regex==2020.9.27
|
||||
redis==3.4.1
|
||||
repoze.lru==0.7
|
||||
requests==2.22.0
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ PYRAMID_SETTINGS = {}
|
|||
EXTENSIONS = {}
|
||||
|
||||
__version__ = ('.'.join((str(each) for each in VERSION[:3])))
|
||||
__dbversion__ = 109 # defines current db version for migrations
|
||||
__dbversion__ = 110 # defines current db version for migrations
|
||||
__platform__ = platform.system()
|
||||
__license__ = 'AGPLv3, and Commercial License'
|
||||
__author__ = 'RhodeCode GmbH'
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class TestCreatePullRequestApi(object):
|
|||
id_, params = build_data(
|
||||
self.apikey_regular, 'create_pull_request', **data)
|
||||
response = api_call(self.app, params)
|
||||
expected_message = 'no commits found'
|
||||
expected_message = 'no commits found for merge between specified references'
|
||||
assert_error(id_, expected_message, given=response.body)
|
||||
|
||||
@pytest.mark.backends("git", "hg")
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from rhodecode.api.tests.utils import (
|
|||
|
||||
@pytest.mark.usefixtures("testuser_api", "app")
|
||||
class TestGetPullRequest(object):
|
||||
|
||||
@pytest.mark.backends("git", "hg")
|
||||
def test_api_get_pull_requests(self, pr_util):
|
||||
pull_request = pr_util.create_pull_request()
|
||||
|
|
@ -40,6 +41,7 @@ class TestGetPullRequest(object):
|
|||
target_ref=pull_request.target_ref,
|
||||
revisions=pull_request.revisions,
|
||||
reviewers=(),
|
||||
observers=(),
|
||||
title=pull_request.title,
|
||||
description=pull_request.description,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class TestUpdatePullRequest(object):
|
|||
"pull_request": response.json['result']['pull_request'],
|
||||
"updated_commits": {"added": [], "common": [], "removed": []},
|
||||
"updated_reviewers": {"added": [], "removed": []},
|
||||
"updated_observers": {"added": [], "removed": []},
|
||||
}
|
||||
|
||||
response_json = response.json['result']
|
||||
|
|
@ -111,6 +112,7 @@ class TestUpdatePullRequest(object):
|
|||
"total": total_commits,
|
||||
"removed": []},
|
||||
"updated_reviewers": {"added": [], "removed": []},
|
||||
"updated_observers": {"added": [], "removed": []},
|
||||
}
|
||||
|
||||
assert_ok(id_, expected, response.body)
|
||||
|
|
@ -122,7 +124,7 @@ class TestUpdatePullRequest(object):
|
|||
b = user_util.create_user()
|
||||
c = user_util.create_user()
|
||||
new_reviewers = [
|
||||
{'username': b.username,'reasons': ['updated via API'],
|
||||
{'username': b.username, 'reasons': ['updated via API'],
|
||||
'mandatory':False},
|
||||
{'username': c.username, 'reasons': ['updated via API'],
|
||||
'mandatory':False},
|
||||
|
|
@ -132,7 +134,7 @@ class TestUpdatePullRequest(object):
|
|||
removed = [a.username]
|
||||
|
||||
pull_request = pr_util.create_pull_request(
|
||||
reviewers=[(a.username, ['added via API'], False, [])])
|
||||
reviewers=[(a.username, ['added via API'], False, 'reviewer', [])])
|
||||
|
||||
id_, params = build_data(
|
||||
self.apikey, 'update_pull_request',
|
||||
|
|
@ -146,6 +148,7 @@ class TestUpdatePullRequest(object):
|
|||
"pull_request": response.json['result']['pull_request'],
|
||||
"updated_commits": {"added": [], "common": [], "removed": []},
|
||||
"updated_reviewers": {"added": added, "removed": removed},
|
||||
"updated_observers": {"added": [], "removed": []},
|
||||
}
|
||||
|
||||
assert_ok(id_, expected, response.body)
|
||||
|
|
|
|||
|
|
@ -26,12 +26,15 @@ from rhodecode.api.utils import (
|
|||
has_superadmin_permission, Optional, OAttr, get_repo_or_error,
|
||||
get_pull_request_or_error, get_commit_or_error, get_user_or_error,
|
||||
validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
|
||||
from rhodecode.lib import channelstream
|
||||
from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
|
||||
from rhodecode.lib.base import vcs_operation_context
|
||||
from rhodecode.lib.utils2 import str2bool
|
||||
from rhodecode.lib.vcs.backends.base import unicode_to_reference
|
||||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
|
||||
from rhodecode.model.db import (
|
||||
Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers)
|
||||
from rhodecode.model.pull_request import PullRequestModel, MergeCheck
|
||||
from rhodecode.model.settings import SettingsModel
|
||||
from rhodecode.model.validation_schema import Invalid
|
||||
|
|
@ -502,16 +505,19 @@ def comment_pull_request(
|
|||
},
|
||||
error : null
|
||||
"""
|
||||
_ = request.translate
|
||||
|
||||
pull_request = get_pull_request_or_error(pullrequestid)
|
||||
if Optional.extract(repoid):
|
||||
repo = get_repo_or_error(repoid)
|
||||
else:
|
||||
repo = pull_request.target_repo
|
||||
|
||||
db_repo_name = repo.repo_name
|
||||
auth_user = apiuser
|
||||
if not isinstance(userid, Optional):
|
||||
is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
|
||||
user=apiuser, repo_name=repo.repo_name)
|
||||
user=apiuser, repo_name=db_repo_name)
|
||||
if has_superadmin_permission(apiuser) or is_repo_admin:
|
||||
apiuser = get_user_or_error(userid)
|
||||
auth_user = apiuser.AuthUser()
|
||||
|
|
@ -596,6 +602,7 @@ def comment_pull_request(
|
|||
extra_recipients=extra_recipients,
|
||||
send_email=send_email
|
||||
)
|
||||
is_inline = comment.is_inline
|
||||
|
||||
if allowed_to_change_status and status:
|
||||
old_calculated_status = pull_request.calculated_review_status()
|
||||
|
|
@ -628,14 +635,39 @@ def comment_pull_request(
|
|||
'comment_id': comment.comment_id if comment else None,
|
||||
'status': {'given': status, 'was_changed': status_change},
|
||||
}
|
||||
|
||||
comment_broadcast_channel = channelstream.comment_channel(
|
||||
db_repo_name, pull_request_obj=pull_request)
|
||||
|
||||
comment_data = data
|
||||
comment_type = 'inline' if is_inline else 'general'
|
||||
channelstream.comment_channelstream_push(
|
||||
request, comment_broadcast_channel, apiuser,
|
||||
_('posted a new {} comment').format(comment_type),
|
||||
comment_data=comment_data)
|
||||
|
||||
return data
|
||||
|
||||
def _reviewers_validation(obj_list):
|
||||
schema = ReviewerListSchema()
|
||||
try:
|
||||
reviewer_objects = schema.deserialize(obj_list)
|
||||
except Invalid as err:
|
||||
raise JSONRPCValidationError(colander_exc=err)
|
||||
|
||||
# validate users
|
||||
for reviewer_object in reviewer_objects:
|
||||
user = get_user_or_error(reviewer_object['username'])
|
||||
reviewer_object['user_id'] = user.user_id
|
||||
return reviewer_objects
|
||||
|
||||
|
||||
@jsonrpc_method()
|
||||
def create_pull_request(
|
||||
request, apiuser, source_repo, target_repo, source_ref, target_ref,
|
||||
owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
|
||||
description_renderer=Optional(''), reviewers=Optional(None)):
|
||||
description_renderer=Optional(''),
|
||||
reviewers=Optional(None), observers=Optional(None)):
|
||||
"""
|
||||
Creates a new pull request.
|
||||
|
||||
|
|
@ -673,6 +705,13 @@ def create_pull_request(
|
|||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
|
||||
:param observers: Set the new pull request observers list.
|
||||
Reviewer defined by review rules will be added automatically to the
|
||||
defined list. This feature is only available in RhodeCode EE
|
||||
:type observers: Optional(list)
|
||||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author']}]
|
||||
"""
|
||||
|
||||
source_db_repo = get_repo_or_error(source_repo)
|
||||
|
|
@ -686,34 +725,39 @@ def create_pull_request(
|
|||
full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
|
||||
full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
|
||||
|
||||
source_commit = get_commit_or_error(full_source_ref, source_db_repo)
|
||||
target_commit = get_commit_or_error(full_target_ref, target_db_repo)
|
||||
get_commit_or_error(full_source_ref, source_db_repo)
|
||||
get_commit_or_error(full_target_ref, target_db_repo)
|
||||
|
||||
reviewer_objects = Optional.extract(reviewers) or []
|
||||
observer_objects = Optional.extract(observers) or []
|
||||
|
||||
# serialize and validate passed in given reviewers
|
||||
if reviewer_objects:
|
||||
schema = ReviewerListSchema()
|
||||
try:
|
||||
reviewer_objects = schema.deserialize(reviewer_objects)
|
||||
except Invalid as err:
|
||||
raise JSONRPCValidationError(colander_exc=err)
|
||||
reviewer_objects = _reviewers_validation(reviewer_objects)
|
||||
|
||||
# validate users
|
||||
for reviewer_object in reviewer_objects:
|
||||
user = get_user_or_error(reviewer_object['username'])
|
||||
reviewer_object['user_id'] = user.user_id
|
||||
if observer_objects:
|
||||
observer_objects = _reviewers_validation(reviewer_objects)
|
||||
|
||||
get_default_reviewers_data, validate_default_reviewers = \
|
||||
get_default_reviewers_data, validate_default_reviewers, validate_observers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
source_ref_obj = unicode_to_reference(full_source_ref)
|
||||
target_ref_obj = unicode_to_reference(full_target_ref)
|
||||
|
||||
# recalculate reviewers logic, to make sure we can validate this
|
||||
default_reviewers_data = get_default_reviewers_data(
|
||||
owner, source_db_repo,
|
||||
source_commit, target_db_repo, target_commit)
|
||||
owner,
|
||||
source_db_repo,
|
||||
source_ref_obj,
|
||||
target_db_repo,
|
||||
target_ref_obj,
|
||||
)
|
||||
|
||||
# now MERGE our given with the calculated
|
||||
reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects
|
||||
# now MERGE our given with the calculated from the default rules
|
||||
just_reviewers = [
|
||||
x for x in default_reviewers_data['reviewers']
|
||||
if x['role'] == PullRequestReviewers.ROLE_REVIEWER]
|
||||
reviewer_objects = just_reviewers + reviewer_objects
|
||||
|
||||
try:
|
||||
reviewers = validate_default_reviewers(
|
||||
|
|
@ -721,9 +765,21 @@ def create_pull_request(
|
|||
except ValueError as e:
|
||||
raise JSONRPCError('Reviewers Validation: {}'.format(e))
|
||||
|
||||
# now MERGE our given with the calculated from the default rules
|
||||
just_observers = [
|
||||
x for x in default_reviewers_data['reviewers']
|
||||
if x['role'] == PullRequestReviewers.ROLE_OBSERVER]
|
||||
observer_objects = just_observers + observer_objects
|
||||
|
||||
try:
|
||||
observers = validate_observers(
|
||||
observer_objects, default_reviewers_data)
|
||||
except ValueError as e:
|
||||
raise JSONRPCError('Observer Validation: {}'.format(e))
|
||||
|
||||
title = Optional.extract(title)
|
||||
if not title:
|
||||
title_source_ref = source_ref.split(':', 2)[1]
|
||||
title_source_ref = source_ref_obj.name
|
||||
title = PullRequestModel().generate_pullrequest_title(
|
||||
source=source_repo,
|
||||
source_ref=title_source_ref,
|
||||
|
|
@ -732,20 +788,17 @@ def create_pull_request(
|
|||
|
||||
diff_info = default_reviewers_data['diff_info']
|
||||
common_ancestor_id = diff_info['ancestor']
|
||||
commits = diff_info['commits']
|
||||
# NOTE(marcink): reversed is consistent with how we open it in the WEB interface
|
||||
commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])]
|
||||
|
||||
if not common_ancestor_id:
|
||||
raise JSONRPCError('no common ancestor found')
|
||||
raise JSONRPCError('no common ancestor found between specified references')
|
||||
|
||||
if not commits:
|
||||
raise JSONRPCError('no commits found')
|
||||
|
||||
# NOTE(marcink): reversed is consistent with how we open it in the WEB interface
|
||||
revisions = [commit.raw_id for commit in reversed(commits)]
|
||||
raise JSONRPCError('no commits found for merge between specified references')
|
||||
|
||||
# recalculate target ref based on ancestor
|
||||
target_ref_type, target_ref_name, __ = full_target_ref.split(':')
|
||||
full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id))
|
||||
full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id))
|
||||
|
||||
# fetch renderer, if set fallback to plain in case of PR
|
||||
rc_config = SettingsModel().get_all_settings()
|
||||
|
|
@ -760,8 +813,9 @@ def create_pull_request(
|
|||
target_repo=target_repo,
|
||||
target_ref=full_target_ref,
|
||||
common_ancestor_id=common_ancestor_id,
|
||||
revisions=revisions,
|
||||
revisions=commits,
|
||||
reviewers=reviewers,
|
||||
observers=observers,
|
||||
title=title,
|
||||
description=description,
|
||||
description_renderer=description_renderer,
|
||||
|
|
@ -781,7 +835,7 @@ def create_pull_request(
|
|||
def update_pull_request(
|
||||
request, apiuser, pullrequestid, repoid=Optional(None),
|
||||
title=Optional(''), description=Optional(''), description_renderer=Optional(''),
|
||||
reviewers=Optional(None), update_commits=Optional(None)):
|
||||
reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)):
|
||||
"""
|
||||
Updates a pull request.
|
||||
|
||||
|
|
@ -803,7 +857,11 @@ def update_pull_request(
|
|||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
|
||||
:param observers: Update pull request observers list with new value.
|
||||
:type observers: Optional(list)
|
||||
Accepts username strings or objects of the format:
|
||||
|
||||
[{'username': 'nick', 'reasons': ['should be aware about this PR']}]
|
||||
:param update_commits: Trigger update of commits for this pull request
|
||||
:type: update_commits: Optional(bool)
|
||||
|
||||
|
|
@ -821,6 +879,12 @@ def update_pull_request(
|
|||
],
|
||||
"removed": []
|
||||
},
|
||||
"updated_observers": {
|
||||
"added": [
|
||||
"username"
|
||||
],
|
||||
"removed": []
|
||||
},
|
||||
"updated_commits": {
|
||||
"added": [
|
||||
"<sha1_hash>"
|
||||
|
|
@ -852,36 +916,14 @@ def update_pull_request(
|
|||
pullrequestid,))
|
||||
|
||||
reviewer_objects = Optional.extract(reviewers) or []
|
||||
|
||||
if reviewer_objects:
|
||||
schema = ReviewerListSchema()
|
||||
try:
|
||||
reviewer_objects = schema.deserialize(reviewer_objects)
|
||||
except Invalid as err:
|
||||
raise JSONRPCValidationError(colander_exc=err)
|
||||
|
||||
# validate users
|
||||
for reviewer_object in reviewer_objects:
|
||||
user = get_user_or_error(reviewer_object['username'])
|
||||
reviewer_object['user_id'] = user.user_id
|
||||
|
||||
get_default_reviewers_data, get_validated_reviewers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
# re-use stored rules
|
||||
reviewer_rules = pull_request.reviewer_data
|
||||
try:
|
||||
reviewers = get_validated_reviewers(
|
||||
reviewer_objects, reviewer_rules)
|
||||
except ValueError as e:
|
||||
raise JSONRPCError('Reviewers Validation: {}'.format(e))
|
||||
else:
|
||||
reviewers = []
|
||||
observer_objects = Optional.extract(observers) or []
|
||||
|
||||
title = Optional.extract(title)
|
||||
description = Optional.extract(description)
|
||||
description_renderer = Optional.extract(description_renderer)
|
||||
|
||||
# Update title/description
|
||||
title_changed = False
|
||||
if title or description:
|
||||
PullRequestModel().edit(
|
||||
pull_request,
|
||||
|
|
@ -890,8 +932,12 @@ def update_pull_request(
|
|||
description_renderer or pull_request.description_renderer,
|
||||
apiuser)
|
||||
Session().commit()
|
||||
title_changed = True
|
||||
|
||||
commit_changes = {"added": [], "common": [], "removed": []}
|
||||
|
||||
# Update commits
|
||||
commits_changed = False
|
||||
if str2bool(Optional.extract(update_commits)):
|
||||
|
||||
if pull_request.pull_request_state != PullRequest.STATE_CREATED:
|
||||
|
|
@ -907,12 +953,44 @@ def update_pull_request(
|
|||
pull_request, db_user)
|
||||
commit_changes = update_response.changes or commit_changes
|
||||
Session().commit()
|
||||
commits_changed = True
|
||||
|
||||
# Update reviewers
|
||||
# serialize and validate passed in given reviewers
|
||||
if reviewer_objects:
|
||||
reviewer_objects = _reviewers_validation(reviewer_objects)
|
||||
|
||||
if observer_objects:
|
||||
observer_objects = _reviewers_validation(reviewer_objects)
|
||||
|
||||
# re-use stored rules
|
||||
default_reviewers_data = pull_request.reviewer_data
|
||||
|
||||
__, validate_default_reviewers, validate_observers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
if reviewer_objects:
|
||||
try:
|
||||
reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
|
||||
except ValueError as e:
|
||||
raise JSONRPCError('Reviewers Validation: {}'.format(e))
|
||||
else:
|
||||
reviewers = []
|
||||
|
||||
if observer_objects:
|
||||
try:
|
||||
observers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
|
||||
except ValueError as e:
|
||||
raise JSONRPCError('Observer Validation: {}'.format(e))
|
||||
else:
|
||||
observers = []
|
||||
|
||||
reviewers_changed = False
|
||||
reviewers_changes = {"added": [], "removed": []}
|
||||
if reviewers:
|
||||
old_calculated_status = pull_request.calculated_review_status()
|
||||
added_reviewers, removed_reviewers = \
|
||||
PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
|
||||
PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance())
|
||||
|
||||
reviewers_changes['added'] = sorted(
|
||||
[get_user_or_error(n).username for n in added_reviewers])
|
||||
|
|
@ -926,13 +1004,35 @@ def update_pull_request(
|
|||
PullRequestModel().trigger_pull_request_hook(
|
||||
pull_request, apiuser, 'review_status_change',
|
||||
data={'status': calculated_status})
|
||||
reviewers_changed = True
|
||||
|
||||
observers_changed = False
|
||||
observers_changes = {"added": [], "removed": []}
|
||||
if observers:
|
||||
added_observers, removed_observers = \
|
||||
PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance())
|
||||
|
||||
observers_changes['added'] = sorted(
|
||||
[get_user_or_error(n).username for n in added_observers])
|
||||
observers_changes['removed'] = sorted(
|
||||
[get_user_or_error(n).username for n in removed_observers])
|
||||
Session().commit()
|
||||
|
||||
reviewers_changed = True
|
||||
|
||||
# push changed to channelstream
|
||||
if commits_changed or reviewers_changed or observers_changed:
|
||||
pr_broadcast_channel = channelstream.pr_channel(pull_request)
|
||||
msg = 'Pull request was updated.'
|
||||
channelstream.pr_update_channelstream_push(
|
||||
request, pr_broadcast_channel, apiuser, msg)
|
||||
|
||||
data = {
|
||||
'msg': 'Updated pull request `{}`'.format(
|
||||
pull_request.pull_request_id),
|
||||
'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id),
|
||||
'pull_request': pull_request.get_api_data(),
|
||||
'updated_commits': commit_changes,
|
||||
'updated_reviewers': reviewers_changes
|
||||
'updated_reviewers': reviewers_changes,
|
||||
'updated_observers': observers_changes,
|
||||
}
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ from rhodecode.api.utils import (
|
|||
get_user_group_or_error, get_user_or_error, validate_repo_permissions,
|
||||
get_perm_or_error, parse_args, get_origin, build_commit_data,
|
||||
validate_set_owner_permissions)
|
||||
from rhodecode.lib import audit_logger, rc_cache
|
||||
from rhodecode.lib import audit_logger, rc_cache, channelstream
|
||||
from rhodecode.lib import repo_maintenance
|
||||
from rhodecode.lib.auth import (
|
||||
HasPermissionAnyApi, HasUserGroupPermissionAnyApi,
|
||||
|
|
@ -1597,10 +1597,13 @@ def comment_commit(
|
|||
}
|
||||
|
||||
"""
|
||||
_ = request.translate
|
||||
|
||||
repo = get_repo_or_error(repoid)
|
||||
if not has_superadmin_permission(apiuser):
|
||||
_perms = ('repository.read', 'repository.write', 'repository.admin')
|
||||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
db_repo_name = repo.repo_name
|
||||
|
||||
try:
|
||||
commit = repo.scm_instance().get_commit(commit_id=commit_id)
|
||||
|
|
@ -1650,6 +1653,8 @@ def comment_commit(
|
|||
extra_recipients=extra_recipients,
|
||||
send_email=send_email
|
||||
)
|
||||
is_inline = comment.is_inline
|
||||
|
||||
if status:
|
||||
# also do a status change
|
||||
try:
|
||||
|
|
@ -1669,6 +1674,17 @@ def comment_commit(
|
|||
data={'comment': comment, 'commit': commit})
|
||||
|
||||
Session().commit()
|
||||
|
||||
comment_broadcast_channel = channelstream.comment_channel(
|
||||
db_repo_name, commit_obj=commit)
|
||||
|
||||
comment_data = {'comment': comment, 'comment_id': comment.comment_id}
|
||||
comment_type = 'inline' if is_inline else 'general'
|
||||
channelstream.comment_channelstream_push(
|
||||
request, comment_broadcast_channel, apiuser,
|
||||
_('posted a new {} comment').format(comment_type),
|
||||
comment_data=comment_data)
|
||||
|
||||
return {
|
||||
'msg': (
|
||||
'Commented on commit `%s` for repository `%s`' % (
|
||||
|
|
|
|||
|
|
@ -474,9 +474,18 @@ class AdminSettingsView(BaseAppView):
|
|||
route_name='admin_settings_issuetracker_test', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
def settings_issuetracker_test(self):
|
||||
return h.urlify_commit_message(
|
||||
error_container = []
|
||||
|
||||
urlified_commit = h.urlify_commit_message(
|
||||
self.request.POST.get('test_text', ''),
|
||||
'repo_group/test_repo1')
|
||||
'repo_group/test_repo1', error_container=error_container)
|
||||
if error_container:
|
||||
def converter(inp):
|
||||
return h.html_escape(unicode(inp))
|
||||
|
||||
return 'ERRORS: ' + '\n'.join(map(converter, error_container))
|
||||
|
||||
return urlified_commit
|
||||
|
||||
@LoginRequired()
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ log = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class DebugStyleView(BaseAppView):
|
||||
|
||||
def load_default_context(self):
|
||||
c = self._get_local_tmpl_context()
|
||||
|
||||
|
|
@ -75,6 +76,7 @@ Check if we should use full-topic or mini-topic.
|
|||
source_ref_parts=AttributeDict(type='branch', name='fix-ticket-2000'),
|
||||
target_ref_parts=AttributeDict(type='branch', name='master'),
|
||||
)
|
||||
|
||||
target_repo = AttributeDict(repo_name='repo_group/target_repo')
|
||||
source_repo = AttributeDict(repo_name='repo_group/source_repo')
|
||||
user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
|
||||
|
|
@ -83,6 +85,7 @@ Check if we should use full-topic or mini-topic.
|
|||
'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
|
||||
'removed': ['eeeeeeeeeee'],
|
||||
})
|
||||
|
||||
file_changes = AttributeDict({
|
||||
'added': ['a/file1.md', 'file2.py'],
|
||||
'modified': ['b/modified_file.rst'],
|
||||
|
|
@ -97,15 +100,19 @@ Check if we should use full-topic or mini-topic.
|
|||
'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',
|
||||
'exc_type': 'AttributeError'
|
||||
}
|
||||
|
||||
email_kwargs = {
|
||||
'test': {},
|
||||
|
||||
'message': {
|
||||
'body': 'message body !'
|
||||
},
|
||||
|
||||
'email_test': {
|
||||
'user': user,
|
||||
'date': datetime.datetime.now(),
|
||||
},
|
||||
|
||||
'exception': {
|
||||
'email_prefix': '[RHODECODE ERROR]',
|
||||
'exc_id': exc_traceback['exc_id'],
|
||||
|
|
@ -113,6 +120,7 @@ Check if we should use full-topic or mini-topic.
|
|||
'exc_type_name': 'NameError',
|
||||
'exc_traceback': exc_traceback,
|
||||
},
|
||||
|
||||
'password_reset': {
|
||||
'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
|
||||
|
||||
|
|
@ -121,6 +129,7 @@ Check if we should use full-topic or mini-topic.
|
|||
'email': 'test@rhodecode.com',
|
||||
'first_admin_email': User.get_first_super_admin().email
|
||||
},
|
||||
|
||||
'password_reset_confirmation': {
|
||||
'new_password': 'new-password-example',
|
||||
'user': user,
|
||||
|
|
@ -128,6 +137,7 @@ Check if we should use full-topic or mini-topic.
|
|||
'email': 'test@rhodecode.com',
|
||||
'first_admin_email': User.get_first_super_admin().email
|
||||
},
|
||||
|
||||
'registration': {
|
||||
'user': user,
|
||||
'date': datetime.datetime.now(),
|
||||
|
|
@ -161,6 +171,7 @@ Check if we should use full-topic or mini-topic.
|
|||
'mention': True,
|
||||
|
||||
},
|
||||
|
||||
'pull_request_comment+status': {
|
||||
'user': user,
|
||||
|
||||
|
|
@ -201,6 +212,7 @@ def db():
|
|||
'mention': True,
|
||||
|
||||
},
|
||||
|
||||
'pull_request_comment+file': {
|
||||
'user': user,
|
||||
|
||||
|
|
@ -303,6 +315,7 @@ This should work better !
|
|||
'renderer_type': 'markdown',
|
||||
'mention': True,
|
||||
},
|
||||
|
||||
'cs_comment+status': {
|
||||
'user': user,
|
||||
'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
|
||||
|
|
@ -328,6 +341,7 @@ This is a multiline comment :)
|
|||
'renderer_type': 'markdown',
|
||||
'mention': True,
|
||||
},
|
||||
|
||||
'cs_comment+file': {
|
||||
'user': user,
|
||||
'commit': AttributeDict(idx=123, raw_id='a' * 40, message='Commit message'),
|
||||
|
|
@ -348,12 +362,37 @@ This is a multiline comment :)
|
|||
'renderer_type': 'markdown',
|
||||
'mention': True,
|
||||
},
|
||||
|
||||
|
||||
'pull_request': {
|
||||
'user': user,
|
||||
'pull_request': pr,
|
||||
'pull_request_commits': [
|
||||
('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
|
||||
my-account: moved email closer to profile as it's similar data just moved outside.
|
||||
'''),
|
||||
('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
|
||||
users: description edit fixes
|
||||
|
||||
- tests
|
||||
- added metatags info
|
||||
'''),
|
||||
],
|
||||
|
||||
'pull_request_target_repo': target_repo,
|
||||
'pull_request_target_repo_url': 'http://target-repo/url',
|
||||
|
||||
'pull_request_source_repo': source_repo,
|
||||
'pull_request_source_repo_url': 'http://source-repo/url',
|
||||
|
||||
'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
|
||||
'user_role': 'reviewer',
|
||||
},
|
||||
|
||||
'pull_request+reviewer_role': {
|
||||
'user': user,
|
||||
'pull_request': pr,
|
||||
'pull_request_commits': [
|
||||
('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
|
||||
my-account: moved email closer to profile as it's similar data just moved outside.
|
||||
'''),
|
||||
('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
|
||||
|
|
@ -371,8 +410,33 @@ users: description edit fixes
|
|||
'pull_request_source_repo_url': 'http://source-repo/url',
|
||||
|
||||
'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
|
||||
}
|
||||
'user_role': 'reviewer',
|
||||
},
|
||||
|
||||
'pull_request+observer_role': {
|
||||
'user': user,
|
||||
'pull_request': pr,
|
||||
'pull_request_commits': [
|
||||
('472d1df03bf7206e278fcedc6ac92b46b01c4e21', '''\
|
||||
my-account: moved email closer to profile as it's similar data just moved outside.
|
||||
'''),
|
||||
('cbfa3061b6de2696c7161ed15ba5c6a0045f90a7', '''\
|
||||
users: description edit fixes
|
||||
|
||||
- tests
|
||||
- added metatags info
|
||||
'''),
|
||||
],
|
||||
|
||||
'pull_request_target_repo': target_repo,
|
||||
'pull_request_target_repo_url': 'http://target-repo/url',
|
||||
|
||||
'pull_request_source_repo': source_repo,
|
||||
'pull_request_source_repo_url': 'http://source-repo/url',
|
||||
|
||||
'pull_request_url': 'http://code.rhodecode.com/_pull-request/123',
|
||||
'user_role': 'observer'
|
||||
}
|
||||
}
|
||||
|
||||
template_type = email_id.split('+')[0]
|
||||
|
|
@ -401,6 +465,7 @@ users: description edit fixes
|
|||
c = self.load_default_context()
|
||||
c.active = os.path.splitext(t_path)[0]
|
||||
c.came_from = ''
|
||||
# NOTE(marcink): extend the email types with variations based on data sets
|
||||
c.email_types = {
|
||||
'cs_comment+file': {},
|
||||
'cs_comment+status': {},
|
||||
|
|
@ -409,6 +474,9 @@ users: description edit fixes
|
|||
'pull_request_comment+status': {},
|
||||
|
||||
'pull_request_update': {},
|
||||
|
||||
'pull_request+reviewer_role': {},
|
||||
'pull_request+observer_role': {},
|
||||
}
|
||||
c.email_types.update(EmailNotificationModel.email_types)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ from rhodecode.lib.auth import (
|
|||
HasRepoGroupPermissionAny, AuthUser)
|
||||
from rhodecode.lib.codeblocks import filenode_as_lines_tokens
|
||||
from rhodecode.lib.index import searcher_from_config
|
||||
from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
|
||||
from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int, safe_str
|
||||
from rhodecode.lib.vcs.nodes import FileNode
|
||||
from rhodecode.model.db import (
|
||||
func, true, or_, case, cast, in_filter_generator, String, Session,
|
||||
|
|
@ -331,7 +331,8 @@ class HomeView(BaseAppView, DataGridAppView):
|
|||
{
|
||||
'id': obj.pull_request_id,
|
||||
'value': org_query,
|
||||
'value_display': 'pull request: `!{} - {}`'.format(obj.pull_request_id, obj.title[:50]),
|
||||
'value_display': 'pull request: `!{} - {}`'.format(
|
||||
obj.pull_request_id, safe_str(obj.title[:50])),
|
||||
'type': 'pull_request',
|
||||
'url': h.route_path('pull_requests_global', pull_request_id=obj.pull_request_id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -734,8 +734,8 @@ class MyAccountView(BaseAppView, DataGridAppView):
|
|||
comments_model = CommentsModel()
|
||||
for pr in pull_requests:
|
||||
repo_id = pr.target_repo_id
|
||||
comments = comments_model.get_all_comments(
|
||||
repo_id, pull_request=pr)
|
||||
comments_count = comments_model.get_all_comments(
|
||||
repo_id, pull_request=pr, count_only=True)
|
||||
owned = pr.user_id == self._rhodecode_user.user_id
|
||||
|
||||
data.append({
|
||||
|
|
@ -760,8 +760,8 @@ class MyAccountView(BaseAppView, DataGridAppView):
|
|||
'author': _render('pullrequest_author',
|
||||
pr.author.full_contact, ),
|
||||
'author_raw': pr.author.full_name,
|
||||
'comments': _render('pullrequest_comments', len(comments)),
|
||||
'comments_raw': len(comments),
|
||||
'comments': _render('pullrequest_comments', comments_count),
|
||||
'comments_raw': comments_count,
|
||||
'closed': pr.is_closed(),
|
||||
'owned': owned
|
||||
})
|
||||
|
|
|
|||
|
|
@ -523,7 +523,9 @@ class TestPullrequestsView(object):
|
|||
pull_request = pr_util.create_pull_request()
|
||||
pull_request_id = pull_request.pull_request_id
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request_id, [(1, ['reason'], False, []), (2, ['reason2'], False, [])],
|
||||
pull_request_id, [
|
||||
(1, ['reason'], False, 'reviewer', []),
|
||||
(2, ['reason2'], False, 'reviewer', [])],
|
||||
pull_request.author)
|
||||
author = pull_request.user_id
|
||||
repo = pull_request.target_repo.repo_id
|
||||
|
|
@ -906,12 +908,13 @@ class TestPullrequestsView(object):
|
|||
|
||||
# Change reviewers and check that a notification was made
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request.pull_request_id, [(1, [], False, [])],
|
||||
pull_request.pull_request_id, [
|
||||
(1, [], False, 'reviewer', [])
|
||||
],
|
||||
pull_request.author)
|
||||
assert len(notifications.all()) == 2
|
||||
|
||||
def test_create_pull_request_stores_ancestor_commit_id(self, backend,
|
||||
csrf_token):
|
||||
def test_create_pull_request_stores_ancestor_commit_id(self, backend, csrf_token):
|
||||
commits = [
|
||||
{'message': 'ancestor',
|
||||
'added': [FileNode('file_A', content='content_of_ancestor')]},
|
||||
|
|
|
|||
|
|
@ -18,14 +18,16 @@
|
|||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
from rhodecode.lib import helpers as h
|
||||
from rhodecode.lib import helpers as h, rc_cache
|
||||
from rhodecode.lib.utils2 import safe_int
|
||||
from rhodecode.model.pull_request import get_diff_info
|
||||
|
||||
REVIEWER_API_VERSION = 'V3'
|
||||
from rhodecode.model.db import PullRequestReviewers
|
||||
# V3 - Reviewers, with default rules data
|
||||
# v4 - Added observers metadata
|
||||
REVIEWER_API_VERSION = 'V4'
|
||||
|
||||
|
||||
def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group=None):
|
||||
def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
|
||||
"""
|
||||
Returns json struct of a reviewer for frontend
|
||||
|
||||
|
|
@ -33,11 +35,15 @@ def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group
|
|||
:param reasons: list of strings of why they are reviewers
|
||||
:param mandatory: bool, to set user as mandatory
|
||||
"""
|
||||
role = role or PullRequestReviewers.ROLE_REVIEWER
|
||||
if role not in PullRequestReviewers.ROLES:
|
||||
raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
|
||||
|
||||
return {
|
||||
'user_id': user.user_id,
|
||||
'reasons': reasons or [],
|
||||
'rules': rules or [],
|
||||
'role': role,
|
||||
'mandatory': mandatory,
|
||||
'user_group': user_group,
|
||||
'username': user.username,
|
||||
|
|
@ -48,21 +54,36 @@ def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group
|
|||
}
|
||||
|
||||
|
||||
def get_default_reviewers_data(
|
||||
current_user, source_repo, source_commit, target_repo, target_commit):
|
||||
def to_reviewers(e):
|
||||
if isinstance(e, (tuple, list)):
|
||||
return map(reviewer_as_json, e)
|
||||
else:
|
||||
return reviewer_as_json(e)
|
||||
|
||||
|
||||
def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
|
||||
include_diff_info=True):
|
||||
"""
|
||||
Return json for default reviewers of a repository
|
||||
"""
|
||||
|
||||
diff_info = get_diff_info(
|
||||
source_repo, source_commit.raw_id, target_repo, target_commit.raw_id)
|
||||
diff_info = {}
|
||||
if include_diff_info:
|
||||
diff_info = get_diff_info(
|
||||
source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
|
||||
|
||||
reasons = ['Default reviewer', 'Repository owner']
|
||||
json_reviewers = [reviewer_as_json(
|
||||
user=target_repo.user, reasons=reasons, mandatory=False, rules=None)]
|
||||
user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
|
||||
|
||||
compute_key = rc_cache.utils.compute_key_from_params(
|
||||
current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
|
||||
source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
|
||||
target_ref.commit_id)
|
||||
|
||||
return {
|
||||
'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
|
||||
'compute_key': compute_key,
|
||||
'diff_info': diff_info,
|
||||
'reviewers': json_reviewers,
|
||||
'rules': {},
|
||||
|
|
@ -73,15 +94,18 @@ def get_default_reviewers_data(
|
|||
def validate_default_reviewers(review_members, reviewer_rules):
|
||||
"""
|
||||
Function to validate submitted reviewers against the saved rules
|
||||
|
||||
"""
|
||||
reviewers = []
|
||||
reviewer_by_id = {}
|
||||
for r in review_members:
|
||||
reviewer_user_id = safe_int(r['user_id'])
|
||||
entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['rules'])
|
||||
entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
|
||||
|
||||
reviewer_by_id[reviewer_user_id] = entry
|
||||
reviewers.append(entry)
|
||||
|
||||
return reviewers
|
||||
|
||||
|
||||
def validate_observers(observer_members, reviewer_rules):
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from rhodecode.apps._base import RepoAppView
|
|||
from rhodecode.apps.file_store import utils as store_utils
|
||||
from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
|
||||
|
||||
from rhodecode.lib import diffs, codeblocks
|
||||
from rhodecode.lib import diffs, codeblocks, channelstream
|
||||
from rhodecode.lib.auth import (
|
||||
LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
|
||||
from rhodecode.lib.ext_json import json
|
||||
|
|
@ -170,7 +170,9 @@ class RepoCommitsView(RepoAppView):
|
|||
)
|
||||
reviewers_duplicates.add(_user_id)
|
||||
|
||||
c.allowed_reviewers = reviewers
|
||||
c.reviewers_count = len(reviewers)
|
||||
c.observers_count = 0
|
||||
|
||||
# from associated statuses, check the pull requests, and
|
||||
# show comments from them
|
||||
for pr in prs:
|
||||
|
|
@ -193,7 +195,7 @@ class RepoCommitsView(RepoAppView):
|
|||
|
||||
for review_obj, member, reasons, mandatory, status in review_statuses:
|
||||
member_reviewer = h.reviewer_as_json(
|
||||
member, reasons=reasons, mandatory=mandatory,
|
||||
member, reasons=reasons, mandatory=mandatory, role=None,
|
||||
user_group=None
|
||||
)
|
||||
|
||||
|
|
@ -207,10 +209,7 @@ class RepoCommitsView(RepoAppView):
|
|||
|
||||
# NOTE(marcink): this uses the same voting logic as in pull-requests
|
||||
c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
|
||||
c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
|
||||
c.repo_name,
|
||||
commit.raw_id
|
||||
)
|
||||
c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
|
||||
|
||||
diff = None
|
||||
# Iterate over ranges (default commit view is always one commit)
|
||||
|
|
@ -414,6 +413,7 @@ class RepoCommitsView(RepoAppView):
|
|||
resolves_comment_id=resolves_comment_id,
|
||||
auth_user=self._rhodecode_user
|
||||
)
|
||||
is_inline = comment.is_inline
|
||||
|
||||
# get status if set !
|
||||
if status:
|
||||
|
|
@ -461,6 +461,16 @@ class RepoCommitsView(RepoAppView):
|
|||
data.update(comment.get_dict())
|
||||
data.update({'rendered_text': rendered_comment})
|
||||
|
||||
comment_broadcast_channel = channelstream.comment_channel(
|
||||
self.db_repo_name, commit_obj=commit)
|
||||
|
||||
comment_data = data
|
||||
comment_type = 'inline' if is_inline else 'general'
|
||||
channelstream.comment_channelstream_push(
|
||||
self.request, comment_broadcast_channel, self._rhodecode_user,
|
||||
_('posted a new {} comment').format(comment_type),
|
||||
comment_data=comment_data)
|
||||
|
||||
return data
|
||||
|
||||
@LoginRequired()
|
||||
|
|
|
|||
|
|
@ -39,14 +39,16 @@ from rhodecode.lib.ext_json import json
|
|||
from rhodecode.lib.auth import (
|
||||
LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
|
||||
NotAnonymous, CSRFRequired)
|
||||
from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int
|
||||
from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
|
||||
from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
|
||||
from rhodecode.lib.vcs.backends.base import (
|
||||
EmptyCommit, UpdateFailureReason, unicode_to_reference)
|
||||
from rhodecode.lib.vcs.exceptions import (
|
||||
CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
|
||||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
from rhodecode.model.db import (
|
||||
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository)
|
||||
func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
|
||||
PullRequestReviewers)
|
||||
from rhodecode.model.forms import PullRequestForm
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.pull_request import PullRequestModel, MergeCheck
|
||||
|
|
@ -104,13 +106,14 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
data = []
|
||||
comments_model = CommentsModel()
|
||||
for pr in pull_requests:
|
||||
comments = comments_model.get_all_comments(
|
||||
self.db_repo.repo_id, pull_request=pr)
|
||||
comments_count = comments_model.get_all_comments(
|
||||
self.db_repo.repo_id, pull_request=pr, count_only=True)
|
||||
|
||||
data.append({
|
||||
'name': _render('pullrequest_name',
|
||||
pr.pull_request_id, pr.pull_request_state,
|
||||
pr.work_in_progress, pr.target_repo.repo_name),
|
||||
pr.work_in_progress, pr.target_repo.repo_name,
|
||||
short=True),
|
||||
'name_raw': pr.pull_request_id,
|
||||
'status': _render('pullrequest_status',
|
||||
pr.calculated_review_status()),
|
||||
|
|
@ -126,8 +129,8 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'author': _render('pullrequest_author',
|
||||
pr.author.full_contact, ),
|
||||
'author_raw': pr.author.full_name,
|
||||
'comments': _render('pullrequest_comments', len(comments)),
|
||||
'comments_raw': len(comments),
|
||||
'comments': _render('pullrequest_comments', comments_count),
|
||||
'comments_raw': comments_count,
|
||||
'closed': pr.is_closed(),
|
||||
})
|
||||
|
||||
|
|
@ -310,8 +313,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
pull_request_id = pull_request.pull_request_id
|
||||
|
||||
c.state_progressing = pull_request.is_state_changing()
|
||||
c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
|
||||
pull_request.target_repo.repo_name, pull_request.pull_request_id)
|
||||
c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
|
||||
|
||||
_new_state = {
|
||||
'created': PullRequest.STATE_CREATED,
|
||||
|
|
@ -454,15 +456,18 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
|
||||
return self._get_template_context(c)
|
||||
|
||||
c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
|
||||
c.reviewers_count = pull_request.reviewers_count
|
||||
c.observers_count = pull_request.observers_count
|
||||
|
||||
# reviewers and statuses
|
||||
c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
|
||||
c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
|
||||
c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
|
||||
|
||||
for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
|
||||
member_reviewer = h.reviewer_as_json(
|
||||
member, reasons=reasons, mandatory=mandatory,
|
||||
role=review_obj.role,
|
||||
user_group=review_obj.rule_user_group_data()
|
||||
)
|
||||
|
||||
|
|
@ -474,6 +479,17 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
|
||||
|
||||
for observer_obj, member in pull_request_at_ver.observers():
|
||||
member_observer = h.reviewer_as_json(
|
||||
member, reasons=[], mandatory=False,
|
||||
role=observer_obj.role,
|
||||
user_group=observer_obj.rule_user_group_data()
|
||||
)
|
||||
member_observer['allowed_to_update'] = c.allowed_to_update
|
||||
c.pull_request_set_observers_data_json['observers'].append(member_observer)
|
||||
|
||||
c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
|
||||
|
||||
general_comments, inline_comments = \
|
||||
self.register_comments_vars(c, pull_request_latest, versions)
|
||||
|
||||
|
|
@ -745,7 +761,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
# current user review statuses for each version
|
||||
c.review_versions = {}
|
||||
if self._rhodecode_user.user_id in c.allowed_reviewers:
|
||||
is_reviewer = PullRequestModel().is_user_reviewer(
|
||||
pull_request, self._rhodecode_user)
|
||||
if is_reviewer:
|
||||
for co in general_comments:
|
||||
if co.author.user_id == self._rhodecode_user.user_id:
|
||||
status = co.status_change
|
||||
|
|
@ -961,13 +979,16 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
}
|
||||
return data
|
||||
|
||||
def _get_existing_ids(self, post_data):
|
||||
return filter(lambda e: e, map(safe_int, aslist(post_data.get('comments'), ',')))
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
'repository.read', 'repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
route_name='pullrequest_comments', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
renderer='string_html', xhr=True)
|
||||
def pullrequest_comments(self):
|
||||
self.load_default_context()
|
||||
|
||||
|
|
@ -997,8 +1018,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
self.register_comments_vars(c, pull_request_latest, versions)
|
||||
all_comments = c.inline_comments_flat + c.comments
|
||||
|
||||
existing_ids = filter(
|
||||
lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
|
||||
existing_ids = self._get_existing_ids(self.request.POST)
|
||||
return _render('comments_table', all_comments, len(all_comments),
|
||||
existing_ids=existing_ids)
|
||||
|
||||
|
|
@ -1008,7 +1028,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'repository.read', 'repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
route_name='pullrequest_todos', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
renderer='string_html', xhr=True)
|
||||
def pullrequest_todos(self):
|
||||
self.load_default_context()
|
||||
|
||||
|
|
@ -1040,8 +1060,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
.get_pull_request_resolved_todos(pull_request)
|
||||
|
||||
all_comments = c.unresolved_comments + c.resolved_comments
|
||||
existing_ids = filter(
|
||||
lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
|
||||
existing_ids = self._get_existing_ids(self.request.POST)
|
||||
return _render('comments_table', all_comments, len(c.unresolved_comments),
|
||||
todo_comments=True, existing_ids=existing_ids)
|
||||
|
||||
|
|
@ -1128,30 +1147,35 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
source_scm = source_db_repo.scm_instance()
|
||||
target_scm = target_db_repo.scm_instance()
|
||||
|
||||
source_commit = source_scm.get_commit(source_ref.split(':')[-1])
|
||||
target_commit = target_scm.get_commit(target_ref.split(':')[-1])
|
||||
source_ref_obj = unicode_to_reference(source_ref)
|
||||
target_ref_obj = unicode_to_reference(target_ref)
|
||||
|
||||
source_commit = source_scm.get_commit(source_ref_obj.commit_id)
|
||||
target_commit = target_scm.get_commit(target_ref_obj.commit_id)
|
||||
|
||||
ancestor = source_scm.get_common_ancestor(
|
||||
source_commit.raw_id, target_commit.raw_id, target_scm)
|
||||
|
||||
# recalculate target ref based on ancestor
|
||||
target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
|
||||
target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
|
||||
target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
|
||||
|
||||
get_default_reviewers_data, validate_default_reviewers = \
|
||||
get_default_reviewers_data, validate_default_reviewers, validate_observers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
# recalculate reviewers logic, to make sure we can validate this
|
||||
reviewer_rules = get_default_reviewers_data(
|
||||
self._rhodecode_db_user, source_db_repo,
|
||||
source_commit, target_db_repo, target_commit)
|
||||
self._rhodecode_db_user,
|
||||
source_db_repo,
|
||||
source_ref_obj,
|
||||
target_db_repo,
|
||||
target_ref_obj,
|
||||
include_diff_info=False)
|
||||
|
||||
given_reviewers = _form['review_members']
|
||||
reviewers = validate_default_reviewers(
|
||||
given_reviewers, reviewer_rules)
|
||||
reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
|
||||
observers = validate_observers(_form['observer_members'], reviewer_rules)
|
||||
|
||||
pullrequest_title = _form['pullrequest_title']
|
||||
title_source_ref = source_ref.split(':', 2)[1]
|
||||
title_source_ref = source_ref_obj.name
|
||||
if not pullrequest_title:
|
||||
pullrequest_title = PullRequestModel().generate_pullrequest_title(
|
||||
source=source_repo,
|
||||
|
|
@ -1172,6 +1196,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
revisions=commit_ids,
|
||||
common_ancestor_id=common_ancestor_id,
|
||||
reviewers=reviewers,
|
||||
observers=observers,
|
||||
title=pullrequest_title,
|
||||
description=description,
|
||||
description_renderer=description_renderer,
|
||||
|
|
@ -1221,20 +1246,28 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
'redirect_url': redirect_url}
|
||||
|
||||
is_state_changing = pull_request.is_state_changing()
|
||||
c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
|
||||
pull_request.target_repo.repo_name, pull_request.pull_request_id)
|
||||
c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
|
||||
|
||||
# only owner or admin can update it
|
||||
allowed_to_update = PullRequestModel().check_user_update(
|
||||
pull_request, self._rhodecode_user)
|
||||
|
||||
if allowed_to_update:
|
||||
controls = peppercorn.parse(self.request.POST.items())
|
||||
force_refresh = str2bool(self.request.POST.get('force_refresh'))
|
||||
|
||||
if 'review_members' in controls:
|
||||
self._update_reviewers(
|
||||
c,
|
||||
pull_request, controls['review_members'],
|
||||
pull_request.reviewer_data)
|
||||
pull_request.reviewer_data,
|
||||
PullRequestReviewers.ROLE_REVIEWER)
|
||||
elif 'observer_members' in controls:
|
||||
self._update_reviewers(
|
||||
c,
|
||||
pull_request, controls['observer_members'],
|
||||
pull_request.reviewer_data,
|
||||
PullRequestReviewers.ROLE_OBSERVER)
|
||||
elif str2bool(self.request.POST.get('update_commits', 'false')):
|
||||
if is_state_changing:
|
||||
log.debug('commits update: forbidden because pull request is in state %s',
|
||||
|
|
@ -1255,6 +1288,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
|
||||
self._edit_pull_request(pull_request)
|
||||
else:
|
||||
log.error('Unhandled update data.')
|
||||
raise HTTPBadRequest()
|
||||
|
||||
return {'response': True,
|
||||
|
|
@ -1262,6 +1296,9 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
raise HTTPForbidden()
|
||||
|
||||
def _edit_pull_request(self, pull_request):
|
||||
"""
|
||||
Edit title and description
|
||||
"""
|
||||
_ = self.request.translate
|
||||
|
||||
try:
|
||||
|
|
@ -1302,27 +1339,15 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
|
||||
msg = _(u'Pull request updated to "{source_commit_id}" with '
|
||||
u'{count_added} added, {count_removed} removed commits. '
|
||||
u'Source of changes: {change_source}')
|
||||
u'Source of changes: {change_source}.')
|
||||
msg = msg.format(
|
||||
source_commit_id=pull_request.source_ref_parts.commit_id,
|
||||
count_added=len(resp.changes.added),
|
||||
count_removed=len(resp.changes.removed),
|
||||
change_source=changed)
|
||||
h.flash(msg, category='success')
|
||||
|
||||
message = msg + (
|
||||
' - <a onclick="window.location.reload()">'
|
||||
'<strong>{}</strong></a>'.format(_('Reload page')))
|
||||
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
channelstream.post_message(
|
||||
c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
|
||||
registry=self.request.registry)
|
||||
channelstream.pr_update_channelstream_push(
|
||||
self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
|
||||
else:
|
||||
msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
|
||||
warning_reasons = [
|
||||
|
|
@ -1332,6 +1357,55 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
category = 'warning' if resp.reason in warning_reasons else 'error'
|
||||
h.flash(msg, category=category)
|
||||
|
||||
def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
|
||||
_ = self.request.translate
|
||||
|
||||
get_default_reviewers_data, validate_default_reviewers, validate_observers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
if role == PullRequestReviewers.ROLE_REVIEWER:
|
||||
try:
|
||||
reviewers = validate_default_reviewers(review_members, reviewer_rules)
|
||||
except ValueError as e:
|
||||
log.error('Reviewers Validation: {}'.format(e))
|
||||
h.flash(e, category='error')
|
||||
return
|
||||
|
||||
old_calculated_status = pull_request.calculated_review_status()
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request, reviewers, self._rhodecode_db_user)
|
||||
|
||||
Session().commit()
|
||||
|
||||
msg = _('Pull request reviewers updated.')
|
||||
h.flash(msg, category='success')
|
||||
channelstream.pr_update_channelstream_push(
|
||||
self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
|
||||
|
||||
# trigger status changed if change in reviewers changes the status
|
||||
calculated_status = pull_request.calculated_review_status()
|
||||
if old_calculated_status != calculated_status:
|
||||
PullRequestModel().trigger_pull_request_hook(
|
||||
pull_request, self._rhodecode_user, 'review_status_change',
|
||||
data={'status': calculated_status})
|
||||
|
||||
elif role == PullRequestReviewers.ROLE_OBSERVER:
|
||||
try:
|
||||
observers = validate_observers(review_members, reviewer_rules)
|
||||
except ValueError as e:
|
||||
log.error('Observers Validation: {}'.format(e))
|
||||
h.flash(e, category='error')
|
||||
return
|
||||
|
||||
PullRequestModel().update_observers(
|
||||
pull_request, observers, self._rhodecode_db_user)
|
||||
|
||||
Session().commit()
|
||||
msg = _('Pull request observers updated.')
|
||||
h.flash(msg, category='success')
|
||||
channelstream.pr_update_channelstream_push(
|
||||
self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
|
|
@ -1408,32 +1482,6 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
msg = merge_resp.merge_status_message
|
||||
h.flash(msg, category='error')
|
||||
|
||||
def _update_reviewers(self, pull_request, review_members, reviewer_rules):
|
||||
_ = self.request.translate
|
||||
|
||||
get_default_reviewers_data, validate_default_reviewers = \
|
||||
PullRequestModel().get_reviewer_functions()
|
||||
|
||||
try:
|
||||
reviewers = validate_default_reviewers(review_members, reviewer_rules)
|
||||
except ValueError as e:
|
||||
log.error('Reviewers Validation: {}'.format(e))
|
||||
h.flash(e, category='error')
|
||||
return
|
||||
|
||||
old_calculated_status = pull_request.calculated_review_status()
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request, reviewers, self._rhodecode_user)
|
||||
h.flash(_('Pull request reviewers updated.'), category='success')
|
||||
Session().commit()
|
||||
|
||||
# trigger status changed if change in reviewers changes the status
|
||||
calculated_status = pull_request.calculated_review_status()
|
||||
if old_calculated_status != calculated_status:
|
||||
PullRequestModel().trigger_pull_request_hook(
|
||||
pull_request, self._rhodecode_user, 'review_status_change',
|
||||
data={'status': calculated_status})
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@HasRepoPermissionAnyDecorator(
|
||||
|
|
@ -1488,8 +1536,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
allowed_to_comment = PullRequestModel().check_user_comment(
|
||||
pull_request, self._rhodecode_user)
|
||||
if not allowed_to_comment:
|
||||
log.debug(
|
||||
'comment: forbidden because pull request is from forbidden repo')
|
||||
log.debug('comment: forbidden because pull request is from forbidden repo')
|
||||
raise HTTPForbidden()
|
||||
|
||||
c = self.load_default_context()
|
||||
|
|
@ -1518,6 +1565,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
pull_request, self._rhodecode_user, self.db_repo, message=text,
|
||||
auth_user=self._rhodecode_user)
|
||||
Session().flush()
|
||||
is_inline = comment.is_inline
|
||||
|
||||
PullRequestModel().trigger_pull_request_hook(
|
||||
pull_request, self._rhodecode_user, 'comment',
|
||||
|
|
@ -1551,6 +1599,7 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
resolves_comment_id=resolves_comment_id,
|
||||
auth_user=self._rhodecode_user
|
||||
)
|
||||
is_inline = comment.is_inline
|
||||
|
||||
if allowed_to_change_status:
|
||||
# calculate old status before we change it
|
||||
|
|
@ -1599,6 +1648,16 @@ class RepoPullRequestsView(RepoAppView, DataGridAppView):
|
|||
data.update(comment.get_dict())
|
||||
data.update({'rendered_text': rendered_comment})
|
||||
|
||||
comment_broadcast_channel = channelstream.comment_channel(
|
||||
self.db_repo_name, pull_request_obj=pull_request)
|
||||
|
||||
comment_data = data
|
||||
comment_type = 'inline' if is_inline else 'general'
|
||||
channelstream.comment_channelstream_push(
|
||||
self.request, comment_broadcast_channel, self._rhodecode_user,
|
||||
_('posted a new {} comment').format(comment_type),
|
||||
comment_data=comment_data)
|
||||
|
||||
return data
|
||||
|
||||
@LoginRequired()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from pyramid.view import view_config
|
|||
from rhodecode.apps._base import RepoAppView
|
||||
from rhodecode.apps.repository.utils import get_default_reviewers_data
|
||||
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
|
||||
from rhodecode.lib.vcs.backends.base import Reference
|
||||
from rhodecode.model.db import Repository
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -61,13 +62,28 @@ class RepoReviewRulesView(RepoAppView):
|
|||
target_repo_name = request.GET.get('target_repo', source_repo_name)
|
||||
target_repo = Repository.get_by_repo_name(target_repo_name)
|
||||
|
||||
source_ref = request.GET['source_ref']
|
||||
target_ref = request.GET['target_ref']
|
||||
source_commit = source_repo.get_commit(source_ref)
|
||||
target_commit = target_repo.get_commit(target_ref)
|
||||
|
||||
current_user = request.user.get_instance()
|
||||
review_data = get_default_reviewers_data(
|
||||
current_user, source_repo, source_commit, target_repo, target_commit)
|
||||
|
||||
source_commit_id = request.GET['source_ref']
|
||||
source_type = request.GET['source_ref_type']
|
||||
source_name = request.GET['source_ref_name']
|
||||
|
||||
target_commit_id = request.GET['target_ref']
|
||||
target_type = request.GET['target_ref_type']
|
||||
target_name = request.GET['target_ref_name']
|
||||
|
||||
try:
|
||||
review_data = get_default_reviewers_data(
|
||||
current_user,
|
||||
source_repo,
|
||||
Reference(source_type, source_name, source_commit_id),
|
||||
target_repo,
|
||||
Reference(target_type, target_name, target_commit_id)
|
||||
)
|
||||
except ValueError:
|
||||
# No common ancestor
|
||||
msg = "No Common ancestor found between target and source reference"
|
||||
log.exception(msg)
|
||||
return {'diff_info': {'error': msg}}
|
||||
|
||||
return review_data
|
||||
|
|
|
|||
|
|
@ -341,6 +341,10 @@ def includeme(config):
|
|||
name='json_ext',
|
||||
factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
|
||||
|
||||
config.add_renderer(
|
||||
name='string_html',
|
||||
factory='rhodecode.lib.string_renderer.html')
|
||||
|
||||
# include RhodeCode plugins
|
||||
includes = aslist(settings.get('rhodecode.includes', []))
|
||||
for inc in includes:
|
||||
|
|
@ -408,6 +412,7 @@ def sanitize_settings_and_apply_defaults(global_config, settings):
|
|||
"""
|
||||
|
||||
settings.setdefault('rhodecode.edition', 'Community Edition')
|
||||
settings.setdefault('rhodecode.edition_id', 'CE')
|
||||
|
||||
if 'mako.default_filters' not in settings:
|
||||
# set custom default filters if we don't have it defined
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ def _commits_as_dict(event, commit_ids, repos):
|
|||
cs_data['permalink_url'] = RepoModel().get_commit_url(
|
||||
repo, cs_data['raw_id'], request=event.request,
|
||||
permalink=True)
|
||||
urlified_message, issues_data = process_patterns(
|
||||
urlified_message, issues_data, errors = process_patterns(
|
||||
cs_data['message'], repo.repo_name)
|
||||
cs_data['issues'] = issues_data
|
||||
cs_data['message_html'] = urlify_commit_message(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -88,6 +88,9 @@ ACTIONS_V1 = {
|
|||
'repo.pull_request.reviewer.add': '',
|
||||
'repo.pull_request.reviewer.delete': '',
|
||||
|
||||
'repo.pull_request.observer.add': '',
|
||||
'repo.pull_request.observer.delete': '',
|
||||
|
||||
'repo.commit.strip': {'commit_id': ''},
|
||||
'repo.commit.comment.create': {'data': {}},
|
||||
'repo.commit.comment.delete': {'data': {}},
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@ def attach_context_attributes(context, request, user_id=None, is_api=None):
|
|||
context.rc_config = rc_config
|
||||
context.rhodecode_version = rhodecode.__version__
|
||||
context.rhodecode_edition = config.get('rhodecode.edition')
|
||||
context.rhodecode_edition_id = config.get('rhodecode.edition_id')
|
||||
# unique secret + version does not leak the version but keep consistency
|
||||
context.rhodecode_version_hash = calculate_version_hash(config)
|
||||
|
||||
|
|
|
|||
|
|
@ -225,14 +225,26 @@ def write_history(config, message):
|
|||
|
||||
def get_connection_validators(registry):
|
||||
validators = []
|
||||
for k, config in registry.rhodecode_plugins.iteritems():
|
||||
for k, config in registry.rhodecode_plugins.items():
|
||||
validator = config.get('channelstream', {}).get('connect_validator')
|
||||
if validator:
|
||||
validators.append(validator)
|
||||
return validators
|
||||
|
||||
|
||||
def get_channelstream_config(registry=None):
|
||||
if not registry:
|
||||
registry = get_current_registry()
|
||||
|
||||
rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
|
||||
channelstream_config = rhodecode_plugins.get('channelstream', {})
|
||||
return channelstream_config
|
||||
|
||||
|
||||
def post_message(channel, message, username, registry=None):
|
||||
channelstream_config = get_channelstream_config(registry)
|
||||
if not channelstream_config.get('enabled'):
|
||||
return
|
||||
|
||||
message_obj = message
|
||||
if isinstance(message, basestring):
|
||||
|
|
@ -242,26 +254,118 @@ def post_message(channel, message, username, registry=None):
|
|||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
if not registry:
|
||||
registry = get_current_registry()
|
||||
|
||||
log.debug('Channelstream: sending notification to channel %s', channel)
|
||||
rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
|
||||
channelstream_config = rhodecode_plugins.get('channelstream', {})
|
||||
if channelstream_config.get('enabled'):
|
||||
payload = {
|
||||
'type': 'message',
|
||||
'timestamp': datetime.datetime.utcnow(),
|
||||
'user': 'system',
|
||||
'exclude_users': [username],
|
||||
'channel': channel,
|
||||
'message': message_obj
|
||||
}
|
||||
payload = {
|
||||
'type': 'message',
|
||||
'timestamp': datetime.datetime.utcnow(),
|
||||
'user': 'system',
|
||||
'exclude_users': [username],
|
||||
'channel': channel,
|
||||
'message': message_obj
|
||||
}
|
||||
|
||||
try:
|
||||
return channelstream_request(
|
||||
channelstream_config, [payload], '/message',
|
||||
raise_exc=False)
|
||||
except ChannelstreamException:
|
||||
log.exception('Failed to send channelstream data')
|
||||
raise
|
||||
try:
|
||||
return channelstream_request(
|
||||
channelstream_config, [payload], '/message',
|
||||
raise_exc=False)
|
||||
except ChannelstreamException:
|
||||
log.exception('Failed to send channelstream data')
|
||||
raise
|
||||
|
||||
|
||||
def _reload_link(label):
|
||||
return (
|
||||
'<a onclick="window.location.reload()">'
|
||||
'<strong>{}</strong>'
|
||||
'</a>'.format(label)
|
||||
)
|
||||
|
||||
|
||||
def pr_channel(pull_request):
|
||||
repo_name = pull_request.target_repo.repo_name
|
||||
pull_request_id = pull_request.pull_request_id
|
||||
channel = '/repo${}$/pr/{}'.format(repo_name, pull_request_id)
|
||||
log.debug('Getting pull-request channelstream broadcast channel: %s', channel)
|
||||
return channel
|
||||
|
||||
|
||||
def comment_channel(repo_name, commit_obj=None, pull_request_obj=None):
|
||||
channel = None
|
||||
if commit_obj:
|
||||
channel = u'/repo${}$/commit/{}'.format(
|
||||
repo_name, commit_obj.raw_id
|
||||
)
|
||||
elif pull_request_obj:
|
||||
channel = u'/repo${}$/pr/{}'.format(
|
||||
repo_name, pull_request_obj.pull_request_id
|
||||
)
|
||||
log.debug('Getting comment channelstream broadcast channel: %s', channel)
|
||||
|
||||
return channel
|
||||
|
||||
|
||||
def pr_update_channelstream_push(request, pr_broadcast_channel, user, msg, **kwargs):
|
||||
"""
|
||||
Channel push on pull request update
|
||||
"""
|
||||
if not pr_broadcast_channel:
|
||||
return
|
||||
|
||||
_ = request.translate
|
||||
|
||||
message = '{} {}'.format(
|
||||
msg,
|
||||
_reload_link(_(' Reload page to load changes')))
|
||||
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
post_message(
|
||||
pr_broadcast_channel, message_obj, user.username,
|
||||
registry=request.registry)
|
||||
|
||||
|
||||
def comment_channelstream_push(request, comment_broadcast_channel, user, msg, **kwargs):
|
||||
"""
|
||||
Channelstream push on comment action, on commit, or pull-request
|
||||
"""
|
||||
if not comment_broadcast_channel:
|
||||
return
|
||||
|
||||
_ = request.translate
|
||||
|
||||
comment_data = kwargs.pop('comment_data', {})
|
||||
user_data = kwargs.pop('user_data', {})
|
||||
comment_id = comment_data.get('comment_id')
|
||||
|
||||
message = '<strong>{}</strong> {} #{}, {}'.format(
|
||||
user.username,
|
||||
msg,
|
||||
comment_id,
|
||||
_reload_link(_('Reload page to see new comments')),
|
||||
)
|
||||
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
post_message(
|
||||
comment_broadcast_channel, message_obj, user.username,
|
||||
registry=request.registry)
|
||||
|
||||
message_obj = {
|
||||
'message': None,
|
||||
'user': user.username,
|
||||
'comment_id': comment_id,
|
||||
'comment_data': comment_data,
|
||||
'user_data': user_data,
|
||||
'topic': '/comment'
|
||||
}
|
||||
post_message(
|
||||
comment_broadcast_channel, message_obj, user.username,
|
||||
registry=request.registry)
|
||||
|
|
|
|||
68
rhodecode/lib/dbmigrate/versions/110_version_4_22_0.py
Normal file
68
rhodecode/lib/dbmigrate/versions/110_version_4_22_0.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from sqlalchemy import *
|
||||
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.operations import Operations
|
||||
|
||||
from rhodecode.lib.dbmigrate.versions import _reset_base
|
||||
from rhodecode.model import meta, init_model_encryption
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
"""
|
||||
Upgrade operations go here.
|
||||
Don't create your own engine; bind migrate_engine to your metadata
|
||||
"""
|
||||
_reset_base(migrate_engine)
|
||||
from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
|
||||
|
||||
init_model_encryption(db)
|
||||
|
||||
context = MigrationContext.configure(migrate_engine.connect())
|
||||
op = Operations(context)
|
||||
|
||||
table = db.RepoReviewRuleUser.__table__
|
||||
with op.batch_alter_table(table.name) as batch_op:
|
||||
new_column = Column('role', Unicode(255), nullable=True)
|
||||
batch_op.add_column(new_column)
|
||||
|
||||
_fill_rule_user_role(op, meta.Session)
|
||||
|
||||
table = db.RepoReviewRuleUserGroup.__table__
|
||||
with op.batch_alter_table(table.name) as batch_op:
|
||||
new_column = Column('role', Unicode(255), nullable=True)
|
||||
batch_op.add_column(new_column)
|
||||
|
||||
_fill_rule_user_group_role(op, meta.Session)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
|
||||
def fixups(models, _SESSION):
|
||||
pass
|
||||
|
||||
|
||||
def _fill_rule_user_role(op, session):
|
||||
params = {'role': 'reviewer'}
|
||||
query = text(
|
||||
'UPDATE repo_review_rules_users SET role = :role'
|
||||
).bindparams(**params)
|
||||
op.execute(query)
|
||||
session().commit()
|
||||
|
||||
|
||||
def _fill_rule_user_group_role(op, session):
|
||||
params = {'role': 'reviewer'}
|
||||
query = text(
|
||||
'UPDATE repo_review_rules_users_groups SET role = :role'
|
||||
).bindparams(**params)
|
||||
op.execute(query)
|
||||
session().commit()
|
||||
|
|
@ -131,7 +131,7 @@ def send_exc_email(request, exc_id, exc_type_name):
|
|||
|
||||
# NOTE(marcink): needed for email template rendering
|
||||
user_id = None
|
||||
if request:
|
||||
if hasattr(request, 'user'):
|
||||
user_id = request.user.user_id
|
||||
attach_context_attributes(TemplateArgs(), request, user_id=user_id, is_api=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import re
|
|||
import time
|
||||
import string
|
||||
import hashlib
|
||||
import regex
|
||||
from collections import OrderedDict
|
||||
|
||||
import pygments
|
||||
|
|
@ -1103,6 +1104,10 @@ def bool2icon(value, show_at_false=True):
|
|||
return HTML.tag('i', class_="icon-false", title='False')
|
||||
return HTML.tag('i')
|
||||
|
||||
|
||||
def b64(inp):
|
||||
return base64.b64encode(inp)
|
||||
|
||||
#==============================================================================
|
||||
# PERMS
|
||||
#==============================================================================
|
||||
|
|
@ -1653,7 +1658,7 @@ def get_active_pattern_entries(repo_name):
|
|||
return active_entries
|
||||
|
||||
|
||||
pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
|
||||
pr_pattern_re = regex.compile(r'(?:(?:^!)|(?: !))(\d+)')
|
||||
|
||||
allowed_link_formats = [
|
||||
'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
|
||||
|
|
@ -1670,6 +1675,7 @@ def process_patterns(text_string, repo_name, link_format='html', active_entries=
|
|||
active_entries = get_active_pattern_entries(repo_name)
|
||||
|
||||
issues_data = []
|
||||
errors = []
|
||||
new_text = text_string
|
||||
|
||||
log.debug('Got %s entries to process', len(active_entries))
|
||||
|
|
@ -1687,9 +1693,11 @@ def process_patterns(text_string, repo_name, link_format='html', active_entries=
|
|||
pattern = entry['pat_compiled']
|
||||
else:
|
||||
try:
|
||||
pattern = re.compile(r'%s' % entry['pat'])
|
||||
except re.error:
|
||||
log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
|
||||
pattern = regex.compile(r'%s' % entry['pat'])
|
||||
except regex.error as e:
|
||||
regex_err = ValueError('{}:{}'.format(entry['pat'], e))
|
||||
log.exception('issue tracker pattern: `%s` failed to compile', regex_err)
|
||||
errors.append(regex_err)
|
||||
continue
|
||||
|
||||
data_func = partial(
|
||||
|
|
@ -1721,11 +1729,11 @@ def process_patterns(text_string, repo_name, link_format='html', active_entries=
|
|||
new_text = pr_pattern_re.sub(pr_url_func, new_text)
|
||||
log.debug('processed !pr pattern')
|
||||
|
||||
return new_text, issues_data
|
||||
return new_text, issues_data, errors
|
||||
|
||||
|
||||
def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None,
|
||||
issues_container=None):
|
||||
issues_container=None, error_container=None):
|
||||
"""
|
||||
Parses given text message and makes proper links.
|
||||
issues are linked to given issue-server, and rest is a commit link
|
||||
|
|
@ -1745,12 +1753,15 @@ def urlify_commit_message(commit_text, repository=None, active_pattern_entries=N
|
|||
new_text = urlify_commits(new_text, repository)
|
||||
|
||||
# process issue tracker patterns
|
||||
new_text, issues = process_patterns(new_text, repository or '',
|
||||
active_entries=active_pattern_entries)
|
||||
new_text, issues, errors = process_patterns(
|
||||
new_text, repository or '', active_entries=active_pattern_entries)
|
||||
|
||||
if issues_container is not None:
|
||||
issues_container.extend(issues)
|
||||
|
||||
if error_container is not None:
|
||||
error_container.extend(errors)
|
||||
|
||||
return literal(new_text)
|
||||
|
||||
|
||||
|
|
@ -1805,7 +1816,7 @@ def render(source, renderer='rst', mentions=False, relative_urls=None,
|
|||
elif renderer == 'rst':
|
||||
if repo_name:
|
||||
# process patterns on comments if we pass in repo name
|
||||
source, issues = process_patterns(
|
||||
source, issues, errors = process_patterns(
|
||||
source, repo_name, link_format='rst',
|
||||
active_entries=active_pattern_entries)
|
||||
if issues_container is not None:
|
||||
|
|
@ -1819,7 +1830,7 @@ def render(source, renderer='rst', mentions=False, relative_urls=None,
|
|||
elif renderer == 'markdown':
|
||||
if repo_name:
|
||||
# process patterns on comments if we pass in repo name
|
||||
source, issues = process_patterns(
|
||||
source, issues, errors = process_patterns(
|
||||
source, repo_name, link_format='markdown',
|
||||
active_entries=active_pattern_entries)
|
||||
if issues_container is not None:
|
||||
|
|
|
|||
35
rhodecode/lib/string_renderer.py
Normal file
35
rhodecode/lib/string_renderer.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2010-2020 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
def html(info):
|
||||
"""
|
||||
Custom string as html content_type renderer for pyramid
|
||||
"""
|
||||
def _render(value, system):
|
||||
request = system.get('request')
|
||||
if request is not None:
|
||||
response = request.response
|
||||
ct = response.content_type
|
||||
if ct == response.default_content_type:
|
||||
response.content_type = 'text/html'
|
||||
return value
|
||||
|
||||
return _render
|
||||
|
|
@ -57,7 +57,43 @@ FILEMODE_DEFAULT = 0o100644
|
|||
FILEMODE_EXECUTABLE = 0o100755
|
||||
EMPTY_COMMIT_ID = '0' * 40
|
||||
|
||||
Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
|
||||
_Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id'))
|
||||
|
||||
|
||||
class Reference(_Reference):
|
||||
|
||||
@property
|
||||
def branch(self):
|
||||
if self.type == 'branch':
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def bookmark(self):
|
||||
if self.type == 'book':
|
||||
return self.name
|
||||
|
||||
|
||||
def unicode_to_reference(raw):
|
||||
"""
|
||||
Convert a unicode (or string) to a reference object.
|
||||
If unicode evaluates to False it returns None.
|
||||
"""
|
||||
if raw:
|
||||
refs = raw.split(':')
|
||||
return Reference(*refs)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def reference_to_unicode(ref):
|
||||
"""
|
||||
Convert a reference object to unicode.
|
||||
If reference is None it returns None.
|
||||
"""
|
||||
if ref:
|
||||
return u':'.join(ref)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class MergeFailureReason(object):
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import collections
|
|||
|
||||
from rhodecode.model import BaseModel
|
||||
from rhodecode.model.db import (
|
||||
ChangesetStatus, ChangesetComment, PullRequest, Session)
|
||||
ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers, Session)
|
||||
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
|
||||
from rhodecode.lib.markup_renderer import (
|
||||
DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
|
||||
|
|
@ -383,15 +383,14 @@ class ChangesetStatusModel(BaseModel):
|
|||
pull_request.source_repo,
|
||||
pull_request=pull_request,
|
||||
with_revisions=True)
|
||||
reviewers = pull_request.get_pull_request_reviewers(
|
||||
role=PullRequestReviewers.ROLE_REVIEWER)
|
||||
return self.aggregate_votes_by_user(_commit_statuses, reviewers)
|
||||
|
||||
return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers)
|
||||
|
||||
def calculated_review_status(self, pull_request, reviewers_statuses=None):
|
||||
def calculated_review_status(self, pull_request):
|
||||
"""
|
||||
calculate pull request status based on reviewers, it should be a list
|
||||
of two element lists.
|
||||
|
||||
:param reviewers_statuses:
|
||||
"""
|
||||
reviewers = reviewers_statuses or self.reviewers_statuses(pull_request)
|
||||
reviewers = self.reviewers_statuses(pull_request)
|
||||
return self.calculate_status(reviewers)
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ class CommentsModel(BaseModel):
|
|||
recipients += [pull_request_obj.author]
|
||||
|
||||
# add the reviewers to notification
|
||||
recipients += [x.user for x in pull_request_obj.reviewers]
|
||||
recipients += [x.user for x in pull_request_obj.get_pull_request_reviewers()]
|
||||
|
||||
pr_target_repo = pull_request_obj.target_repo
|
||||
pr_source_repo = pull_request_obj.source_repo
|
||||
|
|
@ -436,9 +436,8 @@ class CommentsModel(BaseModel):
|
|||
'thread_ids': [pr_url, pr_comment_url],
|
||||
})
|
||||
|
||||
recipients += [self._get_user(u) for u in (extra_recipients or [])]
|
||||
|
||||
if send_email:
|
||||
recipients += [self._get_user(u) for u in (extra_recipients or [])]
|
||||
# pre-generate the subject for notification itself
|
||||
(subject, _e, body_plaintext) = EmailNotificationModel().render_email(
|
||||
notification_type, **kwargs)
|
||||
|
|
@ -463,55 +462,11 @@ class CommentsModel(BaseModel):
|
|||
else:
|
||||
action = 'repo.commit.comment.create'
|
||||
|
||||
comment_id = comment.comment_id
|
||||
comment_data = comment.get_api_data()
|
||||
|
||||
self._log_audit_action(
|
||||
action, {'data': comment_data}, auth_user, comment)
|
||||
|
||||
channel = None
|
||||
if commit_obj:
|
||||
repo_name = repo.repo_name
|
||||
channel = u'/repo${}$/commit/{}'.format(
|
||||
repo_name,
|
||||
commit_obj.raw_id
|
||||
)
|
||||
elif pull_request_obj:
|
||||
repo_name = pr_target_repo.repo_name
|
||||
channel = u'/repo${}$/pr/{}'.format(
|
||||
repo_name,
|
||||
pull_request_obj.pull_request_id
|
||||
)
|
||||
|
||||
if channel:
|
||||
username = user.username
|
||||
message = '<strong>{}</strong> {} #{}, {}'
|
||||
message = message.format(
|
||||
username,
|
||||
_('posted a new comment'),
|
||||
comment_id,
|
||||
_('Refresh the page to see new comments.'))
|
||||
|
||||
message_obj = {
|
||||
'message': message,
|
||||
'level': 'success',
|
||||
'topic': '/notifications'
|
||||
}
|
||||
|
||||
channelstream.post_message(
|
||||
channel, message_obj, user.username,
|
||||
registry=get_current_registry())
|
||||
|
||||
message_obj = {
|
||||
'message': None,
|
||||
'user': username,
|
||||
'comment_id': comment_id,
|
||||
'topic': '/comment'
|
||||
}
|
||||
channelstream.post_message(
|
||||
channel, message_obj, user.username,
|
||||
registry=get_current_registry())
|
||||
|
||||
return comment
|
||||
|
||||
def edit(self, comment_id, text, auth_user, version):
|
||||
|
|
@ -586,17 +541,20 @@ class CommentsModel(BaseModel):
|
|||
|
||||
return comment
|
||||
|
||||
def get_all_comments(self, repo_id, revision=None, pull_request=None):
|
||||
def get_all_comments(self, repo_id, revision=None, pull_request=None, count_only=False):
|
||||
q = ChangesetComment.query()\
|
||||
.filter(ChangesetComment.repo_id == repo_id)
|
||||
if revision:
|
||||
q = q.filter(ChangesetComment.revision == revision)
|
||||
elif pull_request:
|
||||
pull_request = self.__get_pull_request(pull_request)
|
||||
q = q.filter(ChangesetComment.pull_request == pull_request)
|
||||
q = q.filter(ChangesetComment.pull_request_id == pull_request.pull_request_id)
|
||||
else:
|
||||
raise Exception('Please specify commit or pull_request')
|
||||
q = q.order_by(ChangesetComment.created_on)
|
||||
if count_only:
|
||||
return q.count()
|
||||
|
||||
return q.all()
|
||||
|
||||
def get_url(self, comment, request=None, permalink=False, anchor=None):
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ from webhelpers2.text import remove_formatting
|
|||
|
||||
from rhodecode.translation import _
|
||||
from rhodecode.lib.vcs import get_vcs_instance, VCSError
|
||||
from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
|
||||
from rhodecode.lib.vcs.backends.base import (
|
||||
EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
|
||||
from rhodecode.lib.utils2 import (
|
||||
str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
|
||||
time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
|
||||
|
|
@ -3773,12 +3774,12 @@ class ChangesetComment(Base, BaseModel):
|
|||
resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
|
||||
resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
|
||||
|
||||
author = relationship('User', lazy='joined')
|
||||
author = relationship('User', lazy='select')
|
||||
repo = relationship('Repository')
|
||||
status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
|
||||
pull_request = relationship('PullRequest', lazy='joined')
|
||||
pull_request_version = relationship('PullRequestVersion')
|
||||
history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='joined', order_by='ChangesetCommentHistory.version')
|
||||
status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
|
||||
pull_request = relationship('PullRequest', lazy='select')
|
||||
pull_request_version = relationship('PullRequestVersion', lazy='select')
|
||||
history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
|
||||
|
||||
@classmethod
|
||||
def get_users(cls, revision=None, pull_request_id=None):
|
||||
|
|
@ -3983,10 +3984,10 @@ class ChangesetStatus(Base, BaseModel):
|
|||
version = Column('version', Integer(), nullable=False, default=0)
|
||||
pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
|
||||
|
||||
author = relationship('User', lazy='joined')
|
||||
repo = relationship('Repository')
|
||||
comment = relationship('ChangesetComment', lazy='joined')
|
||||
pull_request = relationship('PullRequest', lazy='joined')
|
||||
author = relationship('User', lazy='select')
|
||||
repo = relationship('Repository', lazy='select')
|
||||
comment = relationship('ChangesetComment', lazy='select')
|
||||
pull_request = relationship('PullRequest', lazy='select')
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<%s('%s[v%s]:%s')>" % (
|
||||
|
|
@ -4248,26 +4249,11 @@ class _PullRequestBase(BaseModel):
|
|||
|
||||
@staticmethod
|
||||
def unicode_to_reference(raw):
|
||||
"""
|
||||
Convert a unicode (or string) to a reference object.
|
||||
If unicode evaluates to False it returns None.
|
||||
"""
|
||||
if raw:
|
||||
refs = raw.split(':')
|
||||
return Reference(*refs)
|
||||
else:
|
||||
return None
|
||||
return unicode_to_reference(raw)
|
||||
|
||||
@staticmethod
|
||||
def reference_to_unicode(ref):
|
||||
"""
|
||||
Convert a reference object to unicode.
|
||||
If reference is None it returns None.
|
||||
"""
|
||||
if ref:
|
||||
return u':'.join(ref)
|
||||
else:
|
||||
return None
|
||||
return reference_to_unicode(ref)
|
||||
|
||||
def get_api_data(self, with_merge_state=True):
|
||||
from rhodecode.model.pull_request import PullRequestModel
|
||||
|
|
@ -4465,6 +4451,37 @@ class PullRequest(Base, _PullRequestBase):
|
|||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
return ChangesetStatusModel().reviewers_statuses(self)
|
||||
|
||||
def get_pull_request_reviewers(self, role=None):
|
||||
qry = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
|
||||
if role:
|
||||
qry = qry.filter(PullRequestReviewers.role == role)
|
||||
|
||||
return qry.all()
|
||||
|
||||
@property
|
||||
def reviewers_count(self):
|
||||
qry = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
|
||||
.filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
|
||||
return qry.count()
|
||||
|
||||
@property
|
||||
def observers_count(self):
|
||||
qry = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
|
||||
.filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
|
||||
return qry.count()
|
||||
|
||||
def observers(self):
|
||||
qry = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
|
||||
.filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
|
||||
.all()
|
||||
|
||||
for entry in qry:
|
||||
yield entry, entry.user
|
||||
|
||||
@property
|
||||
def workspace_id(self):
|
||||
from rhodecode.model.pull_request import PullRequestModel
|
||||
|
|
@ -4510,6 +4527,9 @@ class PullRequestVersion(Base, _PullRequestBase):
|
|||
return '<DB:PullRequestVersion at %#x>' % id(self)
|
||||
|
||||
@property
|
||||
def reviewers(self):
|
||||
return self.pull_request.reviewers
|
||||
@property
|
||||
def reviewers(self):
|
||||
return self.pull_request.reviewers
|
||||
|
||||
|
|
@ -4530,6 +4550,9 @@ class PullRequestVersion(Base, _PullRequestBase):
|
|||
def reviewers_statuses(self):
|
||||
return self.pull_request.reviewers_statuses()
|
||||
|
||||
def observers(self):
|
||||
return self.pull_request.observers()
|
||||
|
||||
|
||||
class PullRequestReviewers(Base, BaseModel):
|
||||
__tablename__ = 'pull_request_reviewers'
|
||||
|
|
@ -4538,6 +4561,7 @@ class PullRequestReviewers(Base, BaseModel):
|
|||
)
|
||||
ROLE_REVIEWER = u'reviewer'
|
||||
ROLE_OBSERVER = u'observer'
|
||||
ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
|
||||
|
||||
@hybrid_property
|
||||
def reasons(self):
|
||||
|
|
@ -4589,6 +4613,15 @@ class PullRequestReviewers(Base, BaseModel):
|
|||
|
||||
return user_group_data
|
||||
|
||||
@classmethod
|
||||
def get_pull_request_reviewers(cls, pull_request_id, role=None):
|
||||
qry = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request_id == pull_request_id)
|
||||
if role:
|
||||
qry = qry.filter(PullRequestReviewers.role == role)
|
||||
|
||||
return qry.all()
|
||||
|
||||
def __unicode__(self):
|
||||
return u"<%s('id:%s')>" % (self.__class__.__name__,
|
||||
self.pull_requests_reviewers_id)
|
||||
|
|
@ -4954,16 +4987,21 @@ class RepoReviewRuleUser(Base, BaseModel):
|
|||
__table_args__ = (
|
||||
base_table_args
|
||||
)
|
||||
ROLE_REVIEWER = u'reviewer'
|
||||
ROLE_OBSERVER = u'observer'
|
||||
ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
|
||||
|
||||
repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
|
||||
repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
|
||||
user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
|
||||
mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
|
||||
role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
|
||||
user = relationship('User')
|
||||
|
||||
def rule_data(self):
|
||||
return {
|
||||
'mandatory': self.mandatory
|
||||
'mandatory': self.mandatory,
|
||||
'role': self.role,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -4974,17 +5012,22 @@ class RepoReviewRuleUserGroup(Base, BaseModel):
|
|||
)
|
||||
|
||||
VOTE_RULE_ALL = -1
|
||||
ROLE_REVIEWER = u'reviewer'
|
||||
ROLE_OBSERVER = u'observer'
|
||||
ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
|
||||
|
||||
repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
|
||||
repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
|
||||
users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
|
||||
users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
|
||||
mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
|
||||
role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
|
||||
vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
|
||||
users_group = relationship('UserGroup')
|
||||
|
||||
def rule_data(self):
|
||||
return {
|
||||
'mandatory': self.mandatory,
|
||||
'role': self.role,
|
||||
'vote_rule': self.vote_rule
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -601,6 +601,14 @@ def PullRequestForm(localizer, repo_id):
|
|||
reasons = All()
|
||||
rules = All(v.UniqueList(localizer, convert=int)())
|
||||
mandatory = v.StringBoolean()
|
||||
role = v.String(if_missing='reviewer')
|
||||
|
||||
class ObserverForm(formencode.Schema):
|
||||
user_id = v.Int(not_empty=True)
|
||||
reasons = All()
|
||||
rules = All(v.UniqueList(localizer, convert=int)())
|
||||
mandatory = v.StringBoolean()
|
||||
role = v.String(if_missing='observer')
|
||||
|
||||
class _PullRequestForm(formencode.Schema):
|
||||
allow_extra_fields = True
|
||||
|
|
@ -614,6 +622,7 @@ def PullRequestForm(localizer, repo_id):
|
|||
revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
|
||||
v.UniqueList(localizer)(not_empty=True))
|
||||
review_members = formencode.ForEach(ReviewerForm())
|
||||
observer_members = formencode.ForEach(ObserverForm())
|
||||
pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
|
||||
pullrequest_desc = v.UnicodeString(strip=True, required=False)
|
||||
description_renderer = v.UnicodeString(strip=True, required=False)
|
||||
|
|
|
|||
|
|
@ -154,28 +154,56 @@ def get_diff_info(
|
|||
|
||||
commits = []
|
||||
if get_commit_authors:
|
||||
commits = target_scm.compare(
|
||||
log.debug('Obtaining commit authors from set of commits')
|
||||
_compare_data = target_scm.compare(
|
||||
target_ref, source_ref, source_scm, merge=True,
|
||||
pre_load=["author"])
|
||||
pre_load=["author", "date", "message"]
|
||||
)
|
||||
|
||||
for commit in commits:
|
||||
user = User.get_from_cs_author(commit.author)
|
||||
for commit in _compare_data:
|
||||
# NOTE(marcink): we serialize here, so we don't produce more vcsserver calls on data returned
|
||||
# at this function which is later called via JSON serialization
|
||||
serialized_commit = dict(
|
||||
author=commit.author,
|
||||
date=commit.date,
|
||||
message=commit.message,
|
||||
commit_id=commit.raw_id,
|
||||
raw_id=commit.raw_id
|
||||
)
|
||||
commits.append(serialized_commit)
|
||||
user = User.get_from_cs_author(serialized_commit['author'])
|
||||
if user and user not in commit_authors:
|
||||
commit_authors.append(user)
|
||||
|
||||
# lines
|
||||
if get_authors:
|
||||
log.debug('Calculating authors of changed files')
|
||||
target_commit = source_repo.get_commit(ancestor_id)
|
||||
|
||||
for fname, lines in changed_lines.items():
|
||||
|
||||
try:
|
||||
node = target_commit.get_node(fname)
|
||||
node = target_commit.get_node(fname, pre_load=["is_binary"])
|
||||
except Exception:
|
||||
log.exception("Failed to load node with path %s", fname)
|
||||
continue
|
||||
|
||||
if not isinstance(node, FileNode):
|
||||
continue
|
||||
|
||||
# NOTE(marcink): for binary node we don't do annotation, just use last author
|
||||
if node.is_binary:
|
||||
author = node.last_commit.author
|
||||
email = node.last_commit.author_email
|
||||
|
||||
user = User.get_from_cs_author(author)
|
||||
if user:
|
||||
user_counts[user.user_id] = user_counts.get(user.user_id, 0) + 1
|
||||
author_counts[author] = author_counts.get(author, 0) + 1
|
||||
email_counts[email] = email_counts.get(email, 0) + 1
|
||||
|
||||
continue
|
||||
|
||||
for annotation in node.annotate:
|
||||
line_no, commit_id, get_commit_func, line_text = annotation
|
||||
if line_no in lines:
|
||||
|
|
@ -190,6 +218,8 @@ def get_diff_info(
|
|||
author_counts[author] = author_counts.get(author, 0) + 1
|
||||
email_counts[email] = email_counts.get(email, 0) + 1
|
||||
|
||||
log.debug('Default reviewers processing finished')
|
||||
|
||||
return {
|
||||
'commits': commits,
|
||||
'files': all_files_changes,
|
||||
|
|
@ -260,10 +290,16 @@ class PullRequestModel(BaseModel):
|
|||
_perms = ('repository.admin',)
|
||||
return self._check_perms(_perms, pull_request, user) or owner
|
||||
|
||||
def is_user_reviewer(self, pull_request, user):
|
||||
return user.user_id in [
|
||||
x.user_id for x in
|
||||
pull_request.get_pull_request_reviewers(PullRequestReviewers.ROLE_REVIEWER)
|
||||
if x.user
|
||||
]
|
||||
|
||||
def check_user_change_status(self, pull_request, user, api=False):
|
||||
reviewer = user.user_id in [x.user_id for x in
|
||||
pull_request.reviewers]
|
||||
return self.check_user_update(pull_request, user, api) or reviewer
|
||||
return self.check_user_update(pull_request, user, api) \
|
||||
or self.is_user_reviewer(pull_request, user)
|
||||
|
||||
def check_user_comment(self, pull_request, user):
|
||||
owner = user.user_id == pull_request.user_id
|
||||
|
|
@ -575,7 +611,7 @@ class PullRequestModel(BaseModel):
|
|||
pull_request_display_obj, at_version
|
||||
|
||||
def create(self, created_by, source_repo, source_ref, target_repo,
|
||||
target_ref, revisions, reviewers, title, description=None,
|
||||
target_ref, revisions, reviewers, observers, title, description=None,
|
||||
common_ancestor_id=None,
|
||||
description_renderer=None,
|
||||
reviewer_data=None, translator=None, auth_user=None):
|
||||
|
|
@ -606,7 +642,7 @@ class PullRequestModel(BaseModel):
|
|||
reviewer_ids = set()
|
||||
# members / reviewers
|
||||
for reviewer_object in reviewers:
|
||||
user_id, reasons, mandatory, rules = reviewer_object
|
||||
user_id, reasons, mandatory, role, rules = reviewer_object
|
||||
user = self._get_user(user_id)
|
||||
|
||||
# skip duplicates
|
||||
|
|
@ -620,6 +656,7 @@ class PullRequestModel(BaseModel):
|
|||
reviewer.pull_request = pull_request
|
||||
reviewer.reasons = reasons
|
||||
reviewer.mandatory = mandatory
|
||||
reviewer.role = role
|
||||
|
||||
# NOTE(marcink): pick only first rule for now
|
||||
rule_id = list(rules)[0] if rules else None
|
||||
|
|
@ -653,6 +690,33 @@ class PullRequestModel(BaseModel):
|
|||
Session().add(reviewer)
|
||||
Session().flush()
|
||||
|
||||
for observer_object in observers:
|
||||
user_id, reasons, mandatory, role, rules = observer_object
|
||||
user = self._get_user(user_id)
|
||||
|
||||
# skip duplicates from reviewers
|
||||
if user.user_id in reviewer_ids:
|
||||
continue
|
||||
|
||||
#reviewer_ids.add(user.user_id)
|
||||
|
||||
observer = PullRequestReviewers()
|
||||
observer.user = user
|
||||
observer.pull_request = pull_request
|
||||
observer.reasons = reasons
|
||||
observer.mandatory = mandatory
|
||||
observer.role = role
|
||||
|
||||
# NOTE(marcink): pick only first rule for now
|
||||
rule_id = list(rules)[0] if rules else None
|
||||
rule = RepoReviewRule.get(rule_id) if rule_id else None
|
||||
if rule:
|
||||
# TODO(marcink): do we need this for observers ??
|
||||
pass
|
||||
|
||||
Session().add(observer)
|
||||
Session().flush()
|
||||
|
||||
# Set approval status to "Under Review" for all commits which are
|
||||
# part of this pull request.
|
||||
ChangesetStatusModel().set_status(
|
||||
|
|
@ -678,7 +742,7 @@ class PullRequestModel(BaseModel):
|
|||
MergeCheck.validate(
|
||||
pull_request, auth_user=auth_user, translator=translator)
|
||||
|
||||
self.notify_reviewers(pull_request, reviewer_ids)
|
||||
self.notify_reviewers(pull_request, reviewer_ids, created_by_user)
|
||||
self.trigger_pull_request_hook(pull_request, created_by_user, 'create')
|
||||
|
||||
creation_data = pull_request.get_api_data(with_merge_state=False)
|
||||
|
|
@ -1204,23 +1268,25 @@ class PullRequestModel(BaseModel):
|
|||
|
||||
:param pull_request: the pr to update
|
||||
:param reviewer_data: list of tuples
|
||||
[(user, ['reason1', 'reason2'], mandatory_flag, [rules])]
|
||||
[(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])]
|
||||
:param user: current use who triggers this action
|
||||
"""
|
||||
|
||||
pull_request = self.__get_pull_request(pull_request)
|
||||
if pull_request.is_closed():
|
||||
raise ValueError('This pull request is closed')
|
||||
|
||||
reviewers = {}
|
||||
for user_id, reasons, mandatory, rules in reviewer_data:
|
||||
for user_id, reasons, mandatory, role, rules in reviewer_data:
|
||||
if isinstance(user_id, (int, compat.string_types)):
|
||||
user_id = self._get_user(user_id).user_id
|
||||
reviewers[user_id] = {
|
||||
'reasons': reasons, 'mandatory': mandatory}
|
||||
'reasons': reasons, 'mandatory': mandatory, 'role': role}
|
||||
|
||||
reviewers_ids = set(reviewers.keys())
|
||||
current_reviewers = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.pull_request ==
|
||||
pull_request).all()
|
||||
current_reviewers = PullRequestReviewers.get_pull_request_reviewers(
|
||||
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER)
|
||||
|
||||
current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
|
||||
|
||||
ids_to_add = reviewers_ids.difference(current_reviewers_ids)
|
||||
|
|
@ -1241,16 +1307,19 @@ class PullRequestModel(BaseModel):
|
|||
reviewer.reasons = reviewers[uid]['reasons']
|
||||
# NOTE(marcink): mandatory shouldn't be changed now
|
||||
# reviewer.mandatory = reviewers[uid]['reasons']
|
||||
# NOTE(marcink): role should be hardcoded, so we won't edit it.
|
||||
reviewer.role = PullRequestReviewers.ROLE_REVIEWER
|
||||
Session().add(reviewer)
|
||||
added_audit_reviewers.append(reviewer.get_dict())
|
||||
|
||||
for uid in ids_to_remove:
|
||||
changed = True
|
||||
# NOTE(marcink): we fetch "ALL" reviewers using .all(). This is an edge case
|
||||
# that prevents and fixes cases that we added the same reviewer twice.
|
||||
# NOTE(marcink): we fetch "ALL" reviewers objects using .all().
|
||||
# This is an edge case that handles previous state of having the same reviewer twice.
|
||||
# this CAN happen due to the lack of DB checks
|
||||
reviewers = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.user_id == uid,
|
||||
PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER,
|
||||
PullRequestReviewers.pull_request == pull_request)\
|
||||
.all()
|
||||
|
||||
|
|
@ -1273,7 +1342,90 @@ class PullRequestModel(BaseModel):
|
|||
'repo.pull_request.reviewer.delete', {'old_data': user_data},
|
||||
user, pull_request)
|
||||
|
||||
self.notify_reviewers(pull_request, ids_to_add)
|
||||
self.notify_reviewers(pull_request, ids_to_add, user)
|
||||
return ids_to_add, ids_to_remove
|
||||
|
||||
def update_observers(self, pull_request, observer_data, user):
|
||||
"""
|
||||
Update the observers in the pull request
|
||||
|
||||
:param pull_request: the pr to update
|
||||
:param observer_data: list of tuples
|
||||
[(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])]
|
||||
:param user: current use who triggers this action
|
||||
"""
|
||||
pull_request = self.__get_pull_request(pull_request)
|
||||
if pull_request.is_closed():
|
||||
raise ValueError('This pull request is closed')
|
||||
|
||||
observers = {}
|
||||
for user_id, reasons, mandatory, role, rules in observer_data:
|
||||
if isinstance(user_id, (int, compat.string_types)):
|
||||
user_id = self._get_user(user_id).user_id
|
||||
observers[user_id] = {
|
||||
'reasons': reasons, 'observers': mandatory, 'role': role}
|
||||
|
||||
observers_ids = set(observers.keys())
|
||||
current_observers = PullRequestReviewers.get_pull_request_reviewers(
|
||||
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_OBSERVER)
|
||||
|
||||
current_observers_ids = set([x.user.user_id for x in current_observers])
|
||||
|
||||
ids_to_add = observers_ids.difference(current_observers_ids)
|
||||
ids_to_remove = current_observers_ids.difference(observers_ids)
|
||||
|
||||
log.debug("Adding %s observer", ids_to_add)
|
||||
log.debug("Removing %s observer", ids_to_remove)
|
||||
changed = False
|
||||
added_audit_observers = []
|
||||
removed_audit_observers = []
|
||||
|
||||
for uid in ids_to_add:
|
||||
changed = True
|
||||
_usr = self._get_user(uid)
|
||||
observer = PullRequestReviewers()
|
||||
observer.user = _usr
|
||||
observer.pull_request = pull_request
|
||||
observer.reasons = observers[uid]['reasons']
|
||||
# NOTE(marcink): mandatory shouldn't be changed now
|
||||
# observer.mandatory = observer[uid]['reasons']
|
||||
|
||||
# NOTE(marcink): role should be hardcoded, so we won't edit it.
|
||||
observer.role = PullRequestReviewers.ROLE_OBSERVER
|
||||
Session().add(observer)
|
||||
added_audit_observers.append(observer.get_dict())
|
||||
|
||||
for uid in ids_to_remove:
|
||||
changed = True
|
||||
# NOTE(marcink): we fetch "ALL" reviewers objects using .all().
|
||||
# This is an edge case that handles previous state of having the same reviewer twice.
|
||||
# this CAN happen due to the lack of DB checks
|
||||
observers = PullRequestReviewers.query()\
|
||||
.filter(PullRequestReviewers.user_id == uid,
|
||||
PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER,
|
||||
PullRequestReviewers.pull_request == pull_request)\
|
||||
.all()
|
||||
|
||||
for obj in observers:
|
||||
added_audit_observers.append(obj.get_dict())
|
||||
Session().delete(obj)
|
||||
|
||||
if changed:
|
||||
Session().expire_all()
|
||||
pull_request.updated_on = datetime.datetime.now()
|
||||
Session().add(pull_request)
|
||||
|
||||
# finally store audit logs
|
||||
for user_data in added_audit_observers:
|
||||
self._log_audit_action(
|
||||
'repo.pull_request.observer.add', {'data': user_data},
|
||||
user, pull_request)
|
||||
for user_data in removed_audit_observers:
|
||||
self._log_audit_action(
|
||||
'repo.pull_request.observer.delete', {'old_data': user_data},
|
||||
user, pull_request)
|
||||
|
||||
self.notify_observers(pull_request, ids_to_add, user)
|
||||
return ids_to_add, ids_to_remove
|
||||
|
||||
def get_url(self, pull_request, request=None, permalink=False):
|
||||
|
|
@ -1301,16 +1453,16 @@ class PullRequestModel(BaseModel):
|
|||
pr_url = urllib.unquote(self.get_url(pull_request, request=request))
|
||||
return safe_unicode('{pr_url}/repository'.format(pr_url=pr_url))
|
||||
|
||||
def notify_reviewers(self, pull_request, reviewers_ids):
|
||||
# notification to reviewers
|
||||
if not reviewers_ids:
|
||||
def _notify_reviewers(self, pull_request, user_ids, role, user):
|
||||
# notification to reviewers/observers
|
||||
if not user_ids:
|
||||
return
|
||||
|
||||
log.debug('Notify following reviewers about pull-request %s', reviewers_ids)
|
||||
log.debug('Notify following %s users about pull-request %s', role, user_ids)
|
||||
|
||||
pull_request_obj = pull_request
|
||||
# get the current participants of this pull request
|
||||
recipients = reviewers_ids
|
||||
recipients = user_ids
|
||||
notification_type = EmailNotificationModel.TYPE_PULL_REQUEST
|
||||
|
||||
pr_source_repo = pull_request_obj.source_repo
|
||||
|
|
@ -1332,8 +1484,10 @@ class PullRequestModel(BaseModel):
|
|||
(x.raw_id, x.message)
|
||||
for x in map(pr_source_repo.get_commit, pull_request.revisions)]
|
||||
|
||||
current_rhodecode_user = user
|
||||
kwargs = {
|
||||
'user': pull_request.author,
|
||||
'user': current_rhodecode_user,
|
||||
'pull_request_author': pull_request.author,
|
||||
'pull_request': pull_request_obj,
|
||||
'pull_request_commits': pull_request_commits,
|
||||
|
||||
|
|
@ -1345,6 +1499,7 @@ class PullRequestModel(BaseModel):
|
|||
|
||||
'pull_request_url': pr_url,
|
||||
'thread_ids': [pr_url],
|
||||
'user_role': role
|
||||
}
|
||||
|
||||
# pre-generate the subject for notification itself
|
||||
|
|
@ -1353,7 +1508,7 @@ class PullRequestModel(BaseModel):
|
|||
|
||||
# create notification objects, and emails
|
||||
NotificationModel().create(
|
||||
created_by=pull_request.author,
|
||||
created_by=current_rhodecode_user,
|
||||
notification_subject=subject,
|
||||
notification_body=body_plaintext,
|
||||
notification_type=notification_type,
|
||||
|
|
@ -1361,11 +1516,19 @@ class PullRequestModel(BaseModel):
|
|||
email_kwargs=kwargs,
|
||||
)
|
||||
|
||||
def notify_reviewers(self, pull_request, reviewers_ids, user):
|
||||
return self._notify_reviewers(pull_request, reviewers_ids,
|
||||
PullRequestReviewers.ROLE_REVIEWER, user)
|
||||
|
||||
def notify_observers(self, pull_request, observers_ids, user):
|
||||
return self._notify_reviewers(pull_request, observers_ids,
|
||||
PullRequestReviewers.ROLE_OBSERVER, user)
|
||||
|
||||
def notify_users(self, pull_request, updating_user, ancestor_commit_id,
|
||||
commit_changes, file_changes):
|
||||
|
||||
updating_user_id = updating_user.user_id
|
||||
reviewers = set([x.user.user_id for x in pull_request.reviewers])
|
||||
reviewers = set([x.user.user_id for x in pull_request.get_pull_request_reviewers()])
|
||||
# NOTE(marcink): send notification to all other users except to
|
||||
# person who updated the PR
|
||||
recipients = reviewers.difference(set([updating_user_id]))
|
||||
|
|
@ -1874,11 +2037,13 @@ class PullRequestModel(BaseModel):
|
|||
try:
|
||||
from rc_reviewers.utils import get_default_reviewers_data
|
||||
from rc_reviewers.utils import validate_default_reviewers
|
||||
from rc_reviewers.utils import validate_observers
|
||||
except ImportError:
|
||||
from rhodecode.apps.repository.utils import get_default_reviewers_data
|
||||
from rhodecode.apps.repository.utils import validate_default_reviewers
|
||||
from rhodecode.apps.repository.utils import validate_observers
|
||||
|
||||
return get_default_reviewers_data, validate_default_reviewers
|
||||
return get_default_reviewers_data, validate_default_reviewers, validate_observers
|
||||
|
||||
|
||||
class MergeCheck(object):
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ from rhodecode.lib.exceptions import (
|
|||
from rhodecode.lib.caching_query import FromCache
|
||||
from rhodecode.model import BaseModel
|
||||
from rhodecode.model.db import (
|
||||
_hash_key, true, false, or_, joinedload, User, UserToPerm,
|
||||
_hash_key, func, true, false, or_, joinedload, User, UserToPerm,
|
||||
UserEmailMap, UserIpMap, UserLog)
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.auth_token import AuthTokenModel
|
||||
|
|
@ -96,7 +96,11 @@ class UserModel(BaseModel):
|
|||
User.username.ilike(ilike_expression)
|
||||
)
|
||||
)
|
||||
# sort by len to have top most matches first
|
||||
query = query.order_by(func.length(User.username))\
|
||||
.order_by(User.username)
|
||||
query = query.limit(limit)
|
||||
|
||||
users = query.all()
|
||||
|
||||
_users = [
|
||||
|
|
|
|||
|
|
@ -21,12 +21,17 @@
|
|||
import colander
|
||||
from rhodecode.model.validation_schema import validators, preparers, types
|
||||
|
||||
DEFAULT_ROLE = 'reviewer'
|
||||
VALID_ROLES = ['reviewer', 'observer']
|
||||
|
||||
|
||||
class ReviewerSchema(colander.MappingSchema):
|
||||
username = colander.SchemaNode(types.StrOrIntType())
|
||||
reasons = colander.SchemaNode(colander.List(), missing=['no reason specified'])
|
||||
mandatory = colander.SchemaNode(colander.Boolean(), missing=False)
|
||||
rules = colander.SchemaNode(colander.List(), missing=[])
|
||||
role = colander.SchemaNode(colander.String(), missing=DEFAULT_ROLE,
|
||||
validator=colander.OneOf(VALID_ROLES))
|
||||
|
||||
|
||||
class ReviewerListSchema(colander.SequenceSchema):
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
<li>The server is being restarted.</li>
|
||||
<li>The server is overloaded.</li>
|
||||
<li>The link may be incorrect.</li>
|
||||
<li><a onclick="window.location.reload()">Reload page</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="inner-column">
|
||||
|
|
|
|||
|
|
@ -374,9 +374,6 @@ ul.auth_plugins {
|
|||
background-color: @grey6;
|
||||
}
|
||||
|
||||
.td-status {
|
||||
padding-left: .5em;
|
||||
}
|
||||
.log-container .truncate {
|
||||
height: 2.75em;
|
||||
white-space: pre-line;
|
||||
|
|
@ -384,6 +381,10 @@ ul.auth_plugins {
|
|||
table.rctable .user {
|
||||
padding-left: 0;
|
||||
}
|
||||
.td-status {
|
||||
padding: 0 0px 0px 10px;
|
||||
width: 15px;
|
||||
}
|
||||
table.rctable {
|
||||
td.td-description,
|
||||
.rc-user {
|
||||
|
|
@ -494,7 +495,8 @@ ul.auth_plugins {
|
|||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#add_reviewer_input {
|
||||
#add_reviewer_input,
|
||||
#add_observer_input {
|
||||
padding-top: 10px
|
||||
}
|
||||
|
||||
|
|
@ -1700,10 +1702,35 @@ table.group_members {
|
|||
}
|
||||
|
||||
.reviewer_ac .ac-input {
|
||||
width: 100%;
|
||||
width: 98%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.observer_ac .ac-input {
|
||||
width: 98%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.rule-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rule-table td {
|
||||
|
||||
}
|
||||
|
||||
.rule-table .td-role {
|
||||
width: 100px
|
||||
}
|
||||
|
||||
.rule-table .td-mandatory {
|
||||
width: 100px
|
||||
}
|
||||
|
||||
.rule-table .td-group-votes {
|
||||
width: 150px
|
||||
}
|
||||
|
||||
.compare_view_commits tr{
|
||||
height: 20px;
|
||||
}
|
||||
|
|
@ -2635,6 +2662,7 @@ h3.files_location{
|
|||
li {
|
||||
list-style-type: none
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.grid-filter-box-icon {
|
||||
|
|
|
|||
|
|
@ -477,23 +477,3 @@
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
.rctable.repo_summary {
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 2px;
|
||||
border-collapse: inherit;
|
||||
border-bottom: 0;
|
||||
|
||||
th {
|
||||
background: @grey7;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
td {
|
||||
border-color: #eaeaea;
|
||||
}
|
||||
|
||||
td.td-status {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,25 @@
|
|||
// see style guide documentation for guidelines.
|
||||
|
||||
// TABLES
|
||||
table.rctable.table-bordered {
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 2px;
|
||||
border-collapse: inherit;
|
||||
border-bottom: 0;
|
||||
|
||||
th {
|
||||
background: @grey7;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
td {
|
||||
border-color: #eaeaea;
|
||||
}
|
||||
|
||||
td.td-status {
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.rctable,
|
||||
table.rctable,
|
||||
|
|
@ -306,12 +325,14 @@ table.dataTable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rctable.audit-log {
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TRUNCATING
|
||||
// TODO: lisaq: should this possibly be moved out of tables.less?
|
||||
// for truncated text
|
||||
|
|
@ -426,15 +447,6 @@ table.keyboard-mappings {
|
|||
}
|
||||
}
|
||||
|
||||
// Pull Request List Table
|
||||
#pull_request_list_table.dataTable {
|
||||
|
||||
//TODO: lisa: This needs to be removed once the description is adjusted
|
||||
// for using an expand_commit button (see issue 765)
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
// Settings (no border)
|
||||
table.rctable.dl-settings {
|
||||
|
|
@ -484,9 +496,6 @@ table.trending_language_tbl {
|
|||
|
||||
// Changesets
|
||||
#changesets.rctable {
|
||||
th {
|
||||
padding: 0 1em 0.65em 0;
|
||||
}
|
||||
|
||||
// td must be fixed height for graph
|
||||
td {
|
||||
|
|
|
|||
|
|
@ -344,6 +344,10 @@ mark,
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
#obj_count {
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#readme .title {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Delete',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Stop following this repository',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Submitting...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Löschen',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Stop following this repository',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Submitting...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Delete',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Stop following this repository',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Submitting...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Delete',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Stop following this repository',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Submitting...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Supprimer',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Arrêter de suivre ce dépôt',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Envoi…',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Aggiungi un altro commento',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Elimina',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'Nessun risultato',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Smetti di seguire il repository',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Inoltro...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'Al momento non ci sono richieste di PULL che richiedono il tuo intervento',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': '別のコメントを追加',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': '選択したステータス ({0}) を元にコメントが自動的に設定されます...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': '削除',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'まだリポジトリグループがありません。',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': '結果がありません',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'まだタグがありません。',
|
||||
'No user groups available yet.': 'まだユーザーグループがありません。',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'このリポジトリのフォローをやめる',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': '送信中...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE
|
||||
_gettext('(from usergroup {0})');
|
||||
_gettext('<strong>, and {0} file</strong> changed.');
|
||||
_gettext('<strong>, and {0} files</strong> changed.');
|
||||
_gettext('<strong>{0} file</strong> changed, ');
|
||||
_gettext('<strong>{0} files</strong> changed, ');
|
||||
_gettext('Add another comment');
|
||||
|
|
@ -21,6 +23,8 @@ _gettext('Collapse {0} commits');
|
|||
_gettext('Comment body was not changed.');
|
||||
_gettext('Comment text will be set automatically based on currently selected status ({0}) ...');
|
||||
_gettext('Commit Authors are not allowed to be a reviewer.');
|
||||
_gettext('Compare summary: <strong>{0} commit</strong>');
|
||||
_gettext('Compare summary: <strong>{0} commits</strong>');
|
||||
_gettext('Context file: ');
|
||||
_gettext('Delete');
|
||||
_gettext('Delete this comment?');
|
||||
|
|
@ -58,6 +62,7 @@ _gettext('No repositories present.');
|
|||
_gettext('No repository groups available yet.');
|
||||
_gettext('No repository groups present.');
|
||||
_gettext('No results');
|
||||
_gettext('No review rules set.');
|
||||
_gettext('No ssh keys available yet.');
|
||||
_gettext('No tags available yet.');
|
||||
_gettext('No user groups available yet.');
|
||||
|
|
@ -95,11 +100,13 @@ _gettext('Status Review');
|
|||
_gettext('Stop following this repository');
|
||||
_gettext('Stopped watching this repository');
|
||||
_gettext('Submitting...');
|
||||
_gettext('Switch target repository with the source.');
|
||||
_gettext('Switch to chat');
|
||||
_gettext('Switch to comment');
|
||||
_gettext('TODO comment');
|
||||
_gettext('TODO from comment {0} was fixed.');
|
||||
_gettext('There are currently no open pull requests requiring your participation.');
|
||||
_gettext('There are no commits to merge.');
|
||||
_gettext('There is a later version of file tree available. Click {0} to create a file at the latest tree.');
|
||||
_gettext('There is an existing path `{0}` at this commit.');
|
||||
_gettext('This pull requests will consist of <strong>{0} commit</strong>.');
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Dodaj kolejny komentarz',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Usuń',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Zakończyć obserwację tego repozytorium',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Przesyłanie...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Adicionar outro comentário',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Excluir',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Parar de seguir este repositório',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Enviando...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Добавить другой комментарий',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': 'Удалить',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': 'Отменить наблюдение за репозиторием',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': 'Применение...',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//JS translations map
|
||||
var _TM = {
|
||||
'(from usergroup {0})': '(from usergroup {0})',
|
||||
'<strong>, and {0} file</strong> changed.': '<strong>, and {0} file</strong> changed.',
|
||||
'<strong>, and {0} files</strong> changed.': '<strong>, and {0} files</strong> changed.',
|
||||
'<strong>{0} file</strong> changed, ': '<strong>{0} file</strong> changed, ',
|
||||
'<strong>{0} files</strong> changed, ': '<strong>{0} files</strong> changed, ',
|
||||
'Add another comment': 'Add another comment',
|
||||
|
|
@ -27,6 +29,8 @@ var _TM = {
|
|||
'Comment body was not changed.': 'Comment body was not changed.',
|
||||
'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
|
||||
'Commit Authors are not allowed to be a reviewer.': 'Commit Authors are not allowed to be a reviewer.',
|
||||
'Compare summary: <strong>{0} commit</strong>': 'Compare summary: <strong>{0} commit</strong>',
|
||||
'Compare summary: <strong>{0} commits</strong>': 'Compare summary: <strong>{0} commits</strong>',
|
||||
'Context file: ': 'Context file: ',
|
||||
'Delete': '删除',
|
||||
'Delete this comment?': 'Delete this comment?',
|
||||
|
|
@ -64,6 +68,7 @@ var _TM = {
|
|||
'No repository groups available yet.': 'No repository groups available yet.',
|
||||
'No repository groups present.': 'No repository groups present.',
|
||||
'No results': 'No results',
|
||||
'No review rules set.': 'No review rules set.',
|
||||
'No ssh keys available yet.': 'No ssh keys available yet.',
|
||||
'No tags available yet.': 'No tags available yet.',
|
||||
'No user groups available yet.': 'No user groups available yet.',
|
||||
|
|
@ -101,11 +106,13 @@ var _TM = {
|
|||
'Stop following this repository': '停止关注该版本库',
|
||||
'Stopped watching this repository': 'Stopped watching this repository',
|
||||
'Submitting...': '提交中……',
|
||||
'Switch target repository with the source.': 'Switch target repository with the source.',
|
||||
'Switch to chat': 'Switch to chat',
|
||||
'Switch to comment': 'Switch to comment',
|
||||
'TODO comment': 'TODO comment',
|
||||
'TODO from comment {0} was fixed.': 'TODO from comment {0} was fixed.',
|
||||
'There are currently no open pull requests requiring your participation.': 'There are currently no open pull requests requiring your participation.',
|
||||
'There are no commits to merge.': 'There are no commits to merge.',
|
||||
'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.',
|
||||
'There is an existing path `{0}` at this commit.': 'There is an existing path `{0}` at this commit.',
|
||||
'This pull requests will consist of <strong>{0} commit</strong>.': 'This pull requests will consist of <strong>{0} commit</strong>.',
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ var CommitsController = function () {
|
|||
height: height,
|
||||
x_step: x_step,
|
||||
y_step: 42,
|
||||
dotRadius: 3.5,
|
||||
lineWidth: 2.5
|
||||
dotRadius: 3.8,
|
||||
lineWidth: 2.8
|
||||
};
|
||||
|
||||
var prevCommitsData = this.$graphCanvas.data('commits') || [];
|
||||
|
|
@ -98,11 +98,12 @@ var CommitsController = function () {
|
|||
|
||||
this.setLabelText(edgeData);
|
||||
|
||||
var padding = 90;
|
||||
// main padding from top, aligns the first dot graph
|
||||
var padding = 100;
|
||||
if (prev_link) {
|
||||
padding += 34;
|
||||
|
||||
}
|
||||
|
||||
$('#graph_nodes').css({'padding-top': padding});
|
||||
|
||||
$.each($('.message.truncate'), function(idx, value) {
|
||||
|
|
|
|||
|
|
@ -94,21 +94,26 @@ var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit)
|
|||
};
|
||||
|
||||
|
||||
ReviewersController = function () {
|
||||
window.ReviewersController = function () {
|
||||
var self = this;
|
||||
this.$loadingIndicator = $('.calculate-reviewers');
|
||||
this.$reviewRulesContainer = $('#review_rules');
|
||||
this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
|
||||
this.$userRule = $('.pr-user-rule-container');
|
||||
this.forbidReviewUsers = undefined;
|
||||
this.$reviewMembers = $('#review_members');
|
||||
this.$observerMembers = $('#observer_members');
|
||||
|
||||
this.currentRequest = null;
|
||||
this.diffData = null;
|
||||
this.enabledRules = [];
|
||||
// sync with db.py entries
|
||||
this.ROLE_REVIEWER = 'reviewer';
|
||||
this.ROLE_OBSERVER = 'observer'
|
||||
|
||||
//dummy handler, we might register our own later
|
||||
this.diffDataHandler = function(data){};
|
||||
this.diffDataHandler = function (data) {};
|
||||
|
||||
this.defaultForbidReviewUsers = function () {
|
||||
this.defaultForbidUsers = function () {
|
||||
return [
|
||||
{
|
||||
'username': 'default',
|
||||
|
|
@ -117,6 +122,9 @@ ReviewersController = function () {
|
|||
];
|
||||
};
|
||||
|
||||
// init default forbidden users
|
||||
this.forbidUsers = this.defaultForbidUsers();
|
||||
|
||||
this.hideReviewRules = function () {
|
||||
self.$reviewRulesContainer.hide();
|
||||
$(self.$userRule.selector).hide();
|
||||
|
|
@ -133,11 +141,40 @@ ReviewersController = function () {
|
|||
return '<div>- {0}</div>'.format(ruleText)
|
||||
};
|
||||
|
||||
this.increaseCounter = function(role) {
|
||||
if (role === self.ROLE_REVIEWER) {
|
||||
var $elem = $('#reviewers-cnt')
|
||||
var cnt = parseInt($elem.data('count') || 0)
|
||||
cnt +=1
|
||||
$elem.html(cnt);
|
||||
$elem.data('count', cnt);
|
||||
}
|
||||
else if (role === self.ROLE_OBSERVER) {
|
||||
var $elem = $('#observers-cnt');
|
||||
var cnt = parseInt($elem.data('count') || 0)
|
||||
cnt +=1
|
||||
$elem.html(cnt);
|
||||
$elem.data('count', cnt);
|
||||
}
|
||||
}
|
||||
|
||||
this.resetCounter = function () {
|
||||
var $elem = $('#reviewers-cnt');
|
||||
|
||||
$elem.data('count', 0);
|
||||
$elem.html(0);
|
||||
|
||||
var $elem = $('#observers-cnt');
|
||||
|
||||
$elem.data('count', 0);
|
||||
$elem.html(0);
|
||||
}
|
||||
|
||||
this.loadReviewRules = function (data) {
|
||||
self.diffData = data;
|
||||
|
||||
// reset forbidden Users
|
||||
this.forbidReviewUsers = self.defaultForbidReviewUsers();
|
||||
this.forbidUsers = self.defaultForbidUsers();
|
||||
|
||||
// reset state of review rules
|
||||
self.$rulesList.html('');
|
||||
|
|
@ -148,7 +185,7 @@ ReviewersController = function () {
|
|||
self.addRule(
|
||||
_gettext('All reviewers must vote.'))
|
||||
);
|
||||
return self.forbidReviewUsers
|
||||
return self.forbidUsers
|
||||
}
|
||||
|
||||
if (data.rules.voting !== undefined) {
|
||||
|
|
@ -195,7 +232,7 @@ ReviewersController = function () {
|
|||
}
|
||||
|
||||
if (data.rules.forbid_author_to_review) {
|
||||
self.forbidReviewUsers.push(data.rules_data.pr_author);
|
||||
self.forbidUsers.push(data.rules_data.pr_author);
|
||||
self.$rulesList.append(
|
||||
self.addRule(
|
||||
_gettext('Author is not allowed to be a reviewer.'))
|
||||
|
|
@ -206,9 +243,8 @@ ReviewersController = function () {
|
|||
|
||||
if (data.rules_data.forbidden_users) {
|
||||
$.each(data.rules_data.forbidden_users, function (index, member_data) {
|
||||
self.forbidReviewUsers.push(member_data)
|
||||
self.forbidUsers.push(member_data)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
self.$rulesList.append(
|
||||
|
|
@ -223,9 +259,31 @@ ReviewersController = function () {
|
|||
_gettext('No review rules set.'))
|
||||
}
|
||||
|
||||
return self.forbidReviewUsers
|
||||
return self.forbidUsers
|
||||
};
|
||||
|
||||
this.emptyTables = function () {
|
||||
self.emptyReviewersTable();
|
||||
self.emptyObserversTable();
|
||||
|
||||
// Also reset counters.
|
||||
self.resetCounter();
|
||||
}
|
||||
|
||||
this.emptyReviewersTable = function (withText) {
|
||||
self.$reviewMembers.empty();
|
||||
if (withText !== undefined) {
|
||||
self.$reviewMembers.html(withText)
|
||||
}
|
||||
};
|
||||
|
||||
this.emptyObserversTable = function (withText) {
|
||||
self.$observerMembers.empty();
|
||||
if (withText !== undefined) {
|
||||
self.$observerMembers.html(withText)
|
||||
}
|
||||
}
|
||||
|
||||
this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
|
||||
|
||||
if (self.currentRequest) {
|
||||
|
|
@ -233,19 +291,21 @@ ReviewersController = function () {
|
|||
self.currentRequest.abort();
|
||||
}
|
||||
|
||||
$('.calculate-reviewers').show();
|
||||
// reset reviewer members
|
||||
self.$reviewMembers.empty();
|
||||
self.$loadingIndicator.show();
|
||||
|
||||
// reset reviewer/observe members
|
||||
self.emptyTables();
|
||||
|
||||
prButtonLock(true, null, 'reviewers');
|
||||
$('#user').hide(); // hide user autocomplete before load
|
||||
$('#observer').hide(); //hide observer autocomplete before load
|
||||
|
||||
// lock PR button, so we cannot send PR before it's calculated
|
||||
prButtonLock(true, _gettext('Loading diff ...'), 'compare');
|
||||
|
||||
if (sourceRef.length !== 3 || targetRef.length !== 3) {
|
||||
// don't load defaults in case we're missing some refs...
|
||||
$('.calculate-reviewers').hide();
|
||||
self.$loadingIndicator.hide();
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -253,9 +313,13 @@ ReviewersController = function () {
|
|||
{
|
||||
'repo_name': templateContext.repo_name,
|
||||
'source_repo': sourceRepo,
|
||||
'source_ref_type': sourceRef[0],
|
||||
'source_ref_name': sourceRef[1],
|
||||
'source_ref': sourceRef[2],
|
||||
'target_repo': targetRepo,
|
||||
'target_ref': targetRef[2]
|
||||
'target_ref': targetRef[2],
|
||||
'target_ref_type': sourceRef[0],
|
||||
'target_ref_name': sourceRef[1]
|
||||
});
|
||||
|
||||
self.currentRequest = $.ajax({
|
||||
|
|
@ -268,15 +332,23 @@ ReviewersController = function () {
|
|||
|
||||
// review rules
|
||||
self.loadReviewRules(data);
|
||||
self.handleDiffData(data["diff_info"]);
|
||||
var diffHandled = self.handleDiffData(data["diff_info"]);
|
||||
if (diffHandled === false) {
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.reviewers.length; i++) {
|
||||
var reviewer = data.reviewers[i];
|
||||
self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory);
|
||||
// load reviewer rules from the repo data
|
||||
self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
|
||||
}
|
||||
$('.calculate-reviewers').hide();
|
||||
|
||||
|
||||
self.$loadingIndicator.hide();
|
||||
prButtonLock(false, null, 'reviewers');
|
||||
$('#user').show(); // show user autocomplete after load
|
||||
|
||||
$('#user').show(); // show user autocomplete before load
|
||||
$('#observer').show(); // show observer autocomplete before load
|
||||
|
||||
var commitElements = data["diff_info"]['commits'];
|
||||
|
||||
|
|
@ -292,7 +364,7 @@ ReviewersController = function () {
|
|||
|
||||
},
|
||||
error: function (jqXHR, textStatus, errorThrown) {
|
||||
var prefix = "Loading diff and reviewers failed\n"
|
||||
var prefix = "Loading diff and reviewers/observers failed\n"
|
||||
var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
|
||||
ajaxErrorSwal(message);
|
||||
}
|
||||
|
|
@ -301,7 +373,7 @@ ReviewersController = function () {
|
|||
};
|
||||
|
||||
// check those, refactor
|
||||
this.removeReviewMember = function (reviewer_id, mark_delete) {
|
||||
this.removeMember = function (reviewer_id, mark_delete) {
|
||||
var reviewer = $('#reviewer_{0}'.format(reviewer_id));
|
||||
|
||||
if (typeof (mark_delete) === undefined) {
|
||||
|
|
@ -312,6 +384,7 @@ ReviewersController = function () {
|
|||
if (reviewer) {
|
||||
// now delete the input
|
||||
$('#reviewer_{0} input'.format(reviewer_id)).remove();
|
||||
$('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
|
||||
// mark as to-delete
|
||||
var obj = $('#reviewer_{0}_name'.format(reviewer_id));
|
||||
obj.addClass('to-delete');
|
||||
|
|
@ -322,27 +395,26 @@ ReviewersController = function () {
|
|||
}
|
||||
};
|
||||
|
||||
this.reviewMemberEntry = function () {
|
||||
this.addMember = function (reviewer_obj, reasons, mandatory, role) {
|
||||
|
||||
};
|
||||
|
||||
this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
|
||||
var id = reviewer_obj.user_id;
|
||||
var username = reviewer_obj.username;
|
||||
|
||||
var reasons = reasons || [];
|
||||
var mandatory = mandatory || false;
|
||||
reasons = reasons || [];
|
||||
mandatory = mandatory || false;
|
||||
role = role || self.ROLE_REVIEWER
|
||||
|
||||
// register IDS to check if we don't have this ID already in
|
||||
// register current set IDS to check if we don't have this ID already in
|
||||
// and prevent duplicates
|
||||
var currentIds = [];
|
||||
|
||||
$.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) {
|
||||
$.each($('.reviewer_entry'), function (index, value) {
|
||||
currentIds.push($(value).data('reviewerUserId'))
|
||||
})
|
||||
|
||||
var userAllowedReview = function (userId) {
|
||||
var allowed = true;
|
||||
$.each(self.forbidReviewUsers, function (index, member_data) {
|
||||
$.each(self.forbidUsers, function (index, member_data) {
|
||||
if (parseInt(userId) === member_data['user_id']) {
|
||||
allowed = false;
|
||||
return false // breaks the loop
|
||||
|
|
@ -352,6 +424,7 @@ ReviewersController = function () {
|
|||
};
|
||||
|
||||
var userAllowed = userAllowedReview(id);
|
||||
|
||||
if (!userAllowed) {
|
||||
alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
|
||||
} else {
|
||||
|
|
@ -359,11 +432,13 @@ ReviewersController = function () {
|
|||
var alreadyReviewer = currentIds.indexOf(id) != -1;
|
||||
|
||||
if (alreadyReviewer) {
|
||||
alert(_gettext('User `{0}` already in reviewers').format(username));
|
||||
alert(_gettext('User `{0}` already in reviewers/observers').format(username));
|
||||
} else {
|
||||
|
||||
var reviewerEntry = renderTemplate('reviewMemberEntry', {
|
||||
'member': reviewer_obj,
|
||||
'mandatory': mandatory,
|
||||
'role': role,
|
||||
'reasons': reasons,
|
||||
'allowed_to_update': true,
|
||||
'review_status': 'not_reviewed',
|
||||
|
|
@ -372,20 +447,36 @@ ReviewersController = function () {
|
|||
'create': true,
|
||||
'rule_show': true,
|
||||
})
|
||||
$(self.$reviewMembers.selector).append(reviewerEntry);
|
||||
|
||||
if (role === self.ROLE_REVIEWER) {
|
||||
$(self.$reviewMembers.selector).append(reviewerEntry);
|
||||
self.increaseCounter(self.ROLE_REVIEWER);
|
||||
$('#reviewer-empty-msg').remove()
|
||||
}
|
||||
else if (role === self.ROLE_OBSERVER) {
|
||||
$(self.$observerMembers.selector).append(reviewerEntry);
|
||||
self.increaseCounter(self.ROLE_OBSERVER);
|
||||
$('#observer-empty-msg').remove();
|
||||
}
|
||||
|
||||
tooltipActivate();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.updateReviewers = function (repo_name, pull_request_id) {
|
||||
var postData = $('#reviewers input').serialize();
|
||||
_updatePullRequest(repo_name, pull_request_id, postData);
|
||||
this.updateReviewers = function (repo_name, pull_request_id, role) {
|
||||
if (role === 'reviewer') {
|
||||
var postData = $('#reviewers input').serialize();
|
||||
_updatePullRequest(repo_name, pull_request_id, postData);
|
||||
} else if (role === 'observer') {
|
||||
var postData = $('#observers input').serialize();
|
||||
_updatePullRequest(repo_name, pull_request_id, postData);
|
||||
}
|
||||
};
|
||||
|
||||
this.handleDiffData = function (data) {
|
||||
self.diffDataHandler(data)
|
||||
return self.diffDataHandler(data)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -449,35 +540,26 @@ var editPullRequest = function(repo_name, pull_request_id, title, description, r
|
|||
|
||||
|
||||
/**
|
||||
* Reviewer autocomplete
|
||||
* autocomplete handler for reviewers/observers
|
||||
*/
|
||||
var ReviewerAutoComplete = function(inputId) {
|
||||
$(inputId).autocomplete({
|
||||
serviceUrl: pyroutes.url('user_autocomplete_data'),
|
||||
minChars:2,
|
||||
maxHeight:400,
|
||||
deferRequestBy: 300, //miliseconds
|
||||
showNoSuggestionNotice: true,
|
||||
tabDisabled: true,
|
||||
autoSelectFirst: true,
|
||||
params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
|
||||
formatResult: autocompleteFormatResult,
|
||||
lookupFilter: autocompleteFilterResult,
|
||||
onSelect: function(element, data) {
|
||||
var autoCompleteHandler = function (inputId, controller, role) {
|
||||
|
||||
return function (element, data) {
|
||||
var mandatory = false;
|
||||
var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
|
||||
var reasons = [_gettext('added manually by "{0}"').format(
|
||||
templateContext.rhodecode_user.username)];
|
||||
|
||||
// add whole user groups
|
||||
if (data.value_type == 'user_group') {
|
||||
reasons.push(_gettext('member of "{0}"').format(data.value_display));
|
||||
|
||||
$.each(data.members, function(index, member_data) {
|
||||
$.each(data.members, function (index, member_data) {
|
||||
var reviewer = member_data;
|
||||
reviewer['user_id'] = member_data['id'];
|
||||
reviewer['gravatar_link'] = member_data['icon_link'];
|
||||
reviewer['user_link'] = member_data['profile_link'];
|
||||
reviewer['rules'] = [];
|
||||
reviewersController.addReviewMember(reviewer, reasons, mandatory);
|
||||
controller.addMember(reviewer, reasons, mandatory, role);
|
||||
})
|
||||
}
|
||||
// add single user
|
||||
|
|
@ -487,14 +569,71 @@ var ReviewerAutoComplete = function(inputId) {
|
|||
reviewer['gravatar_link'] = data['icon_link'];
|
||||
reviewer['user_link'] = data['profile_link'];
|
||||
reviewer['rules'] = [];
|
||||
reviewersController.addReviewMember(reviewer, reasons, mandatory);
|
||||
controller.addMember(reviewer, reasons, mandatory, role);
|
||||
}
|
||||
|
||||
$(inputId).val('');
|
||||
$(inputId).val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reviewer autocomplete
|
||||
*/
|
||||
var ReviewerAutoComplete = function (inputId, controller) {
|
||||
var self = this;
|
||||
self.controller = controller;
|
||||
self.inputId = inputId;
|
||||
var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
|
||||
|
||||
$(inputId).autocomplete({
|
||||
serviceUrl: pyroutes.url('user_autocomplete_data'),
|
||||
minChars: 2,
|
||||
maxHeight: 400,
|
||||
deferRequestBy: 300, //miliseconds
|
||||
showNoSuggestionNotice: true,
|
||||
tabDisabled: true,
|
||||
autoSelectFirst: true,
|
||||
params: {
|
||||
user_id: templateContext.rhodecode_user.user_id,
|
||||
user_groups: true,
|
||||
user_groups_expand: true,
|
||||
skip_default_user: true
|
||||
},
|
||||
formatResult: autocompleteFormatResult,
|
||||
lookupFilter: autocompleteFilterResult,
|
||||
onSelect: handler
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Observers autocomplete
|
||||
*/
|
||||
var ObserverAutoComplete = function(inputId, controller) {
|
||||
var self = this;
|
||||
self.controller = controller;
|
||||
self.inputId = inputId;
|
||||
var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
|
||||
|
||||
$(inputId).autocomplete({
|
||||
serviceUrl: pyroutes.url('user_autocomplete_data'),
|
||||
minChars: 2,
|
||||
maxHeight: 400,
|
||||
deferRequestBy: 300, //miliseconds
|
||||
showNoSuggestionNotice: true,
|
||||
tabDisabled: true,
|
||||
autoSelectFirst: true,
|
||||
params: {
|
||||
user_id: templateContext.rhodecode_user.user_id,
|
||||
user_groups: true,
|
||||
user_groups_expand: true,
|
||||
skip_default_user: true
|
||||
},
|
||||
formatResult: autocompleteFormatResult,
|
||||
lookupFilter: autocompleteFilterResult,
|
||||
onSelect: handler
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
window.VersionController = function () {
|
||||
var self = this;
|
||||
|
|
@ -504,7 +643,7 @@ window.VersionController = function () {
|
|||
|
||||
this.adjustRadioSelectors = function (curNode) {
|
||||
var getVal = function (item) {
|
||||
if (item == 'latest') {
|
||||
if (item === 'latest') {
|
||||
return Number.MAX_SAFE_INTEGER
|
||||
}
|
||||
else {
|
||||
|
|
@ -663,6 +802,7 @@ window.UpdatePrController = function () {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reviewer display panel
|
||||
*/
|
||||
|
|
@ -673,6 +813,7 @@ window.ReviewersPanel = {
|
|||
removeButtons: null,
|
||||
reviewRules: null,
|
||||
setReviewers: null,
|
||||
controller: null,
|
||||
|
||||
setSelectors: function () {
|
||||
var self = this;
|
||||
|
|
@ -682,17 +823,18 @@ window.ReviewersPanel = {
|
|||
self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
|
||||
},
|
||||
|
||||
init: function (reviewRules, setReviewers) {
|
||||
init: function (controller, reviewRules, setReviewers) {
|
||||
var self = this;
|
||||
self.setSelectors();
|
||||
|
||||
this.reviewRules = reviewRules;
|
||||
this.setReviewers = setReviewers;
|
||||
self.controller = controller;
|
||||
self.reviewRules = reviewRules;
|
||||
self.setReviewers = setReviewers;
|
||||
|
||||
this.editButton.on('click', function (e) {
|
||||
self.editButton.on('click', function (e) {
|
||||
self.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
self.closeButton.on('click', function (e) {
|
||||
self.close();
|
||||
self.renderReviewers();
|
||||
});
|
||||
|
|
@ -702,26 +844,134 @@ window.ReviewersPanel = {
|
|||
},
|
||||
|
||||
renderReviewers: function () {
|
||||
var self = this;
|
||||
|
||||
if (self.setReviewers.reviewers === undefined) {
|
||||
return
|
||||
}
|
||||
if (self.setReviewers.reviewers.length === 0) {
|
||||
self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
|
||||
return
|
||||
}
|
||||
|
||||
self.controller.emptyReviewersTable();
|
||||
|
||||
$.each(self.setReviewers.reviewers, function (key, val) {
|
||||
|
||||
$('#review_members').html('')
|
||||
$.each(this.setReviewers.reviewers, function (key, val) {
|
||||
var member = val;
|
||||
if (member.role === self.controller.ROLE_REVIEWER) {
|
||||
var entry = renderTemplate('reviewMemberEntry', {
|
||||
'member': member,
|
||||
'mandatory': member.mandatory,
|
||||
'role': member.role,
|
||||
'reasons': member.reasons,
|
||||
'allowed_to_update': member.allowed_to_update,
|
||||
'review_status': member.review_status,
|
||||
'review_status_label': member.review_status_label,
|
||||
'user_group': member.user_group,
|
||||
'create': false
|
||||
});
|
||||
|
||||
var entry = renderTemplate('reviewMemberEntry', {
|
||||
'member': member,
|
||||
'mandatory': member.mandatory,
|
||||
'reasons': member.reasons,
|
||||
'allowed_to_update': member.allowed_to_update,
|
||||
'review_status': member.review_status,
|
||||
'review_status_label': member.review_status_label,
|
||||
'user_group': member.user_group,
|
||||
'create': false
|
||||
});
|
||||
|
||||
$('#review_members').append(entry)
|
||||
$(self.controller.$reviewMembers.selector).append(entry)
|
||||
}
|
||||
});
|
||||
tooltipActivate();
|
||||
|
||||
tooltipActivate();
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
var self = this;
|
||||
self.editButton.hide();
|
||||
self.closeButton.show();
|
||||
self.addButton.show();
|
||||
$(self.removeButtons.selector).css('visibility', 'visible');
|
||||
// review rules
|
||||
self.controller.loadReviewRules(this.reviewRules);
|
||||
},
|
||||
|
||||
close: function (event) {
|
||||
var self = this;
|
||||
this.editButton.show();
|
||||
this.closeButton.hide();
|
||||
this.addButton.hide();
|
||||
$(this.removeButtons.selector).css('visibility', 'hidden');
|
||||
// hide review rules
|
||||
self.controller.hideReviewRules();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reviewer display panel
|
||||
*/
|
||||
window.ObserversPanel = {
|
||||
editButton: null,
|
||||
closeButton: null,
|
||||
addButton: null,
|
||||
removeButtons: null,
|
||||
reviewRules: null,
|
||||
setReviewers: null,
|
||||
controller: null,
|
||||
|
||||
setSelectors: function () {
|
||||
var self = this;
|
||||
self.editButton = $('#open_edit_observers');
|
||||
self.closeButton =$('#close_edit_observers');
|
||||
self.addButton = $('#add_observer');
|
||||
self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
|
||||
},
|
||||
|
||||
init: function (controller, reviewRules, setReviewers) {
|
||||
var self = this;
|
||||
self.setSelectors();
|
||||
|
||||
self.controller = controller;
|
||||
self.reviewRules = reviewRules;
|
||||
self.setReviewers = setReviewers;
|
||||
|
||||
self.editButton.on('click', function (e) {
|
||||
self.edit();
|
||||
});
|
||||
self.closeButton.on('click', function (e) {
|
||||
self.close();
|
||||
self.renderObservers();
|
||||
});
|
||||
|
||||
self.renderObservers();
|
||||
|
||||
},
|
||||
|
||||
renderObservers: function () {
|
||||
var self = this;
|
||||
if (self.setReviewers.observers === undefined) {
|
||||
return
|
||||
}
|
||||
if (self.setReviewers.observers.length === 0) {
|
||||
self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
|
||||
return
|
||||
}
|
||||
|
||||
self.controller.emptyObserversTable();
|
||||
|
||||
$.each(self.setReviewers.observers, function (key, val) {
|
||||
var member = val;
|
||||
if (member.role === self.controller.ROLE_OBSERVER) {
|
||||
var entry = renderTemplate('reviewMemberEntry', {
|
||||
'member': member,
|
||||
'mandatory': member.mandatory,
|
||||
'role': member.role,
|
||||
'reasons': member.reasons,
|
||||
'allowed_to_update': member.allowed_to_update,
|
||||
'review_status': member.review_status,
|
||||
'review_status_label': member.review_status_label,
|
||||
'user_group': member.user_group,
|
||||
'create': false
|
||||
});
|
||||
|
||||
$(self.controller.$observerMembers.selector).append(entry)
|
||||
}
|
||||
});
|
||||
|
||||
tooltipActivate();
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
|
|
@ -729,8 +979,6 @@ window.ReviewersPanel = {
|
|||
this.closeButton.show();
|
||||
this.addButton.show();
|
||||
$(this.removeButtons.selector).css('visibility', 'visible');
|
||||
// review rules
|
||||
reviewersController.loadReviewRules(this.reviewRules);
|
||||
},
|
||||
|
||||
close: function (event) {
|
||||
|
|
@ -738,11 +986,55 @@ window.ReviewersPanel = {
|
|||
this.closeButton.hide();
|
||||
this.addButton.hide();
|
||||
$(this.removeButtons.selector).css('visibility', 'hidden');
|
||||
// hide review rules
|
||||
reviewersController.hideReviewRules()
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
window.PRDetails = {
|
||||
editButton: null,
|
||||
closeButton: null,
|
||||
deleteButton: null,
|
||||
viewFields: null,
|
||||
editFields: null,
|
||||
|
||||
setSelectors: function () {
|
||||
var self = this;
|
||||
self.editButton = $('#open_edit_pullrequest')
|
||||
self.closeButton = $('#close_edit_pullrequest')
|
||||
self.deleteButton = $('#delete_pullrequest')
|
||||
self.viewFields = $('#pr-desc, #pr-title')
|
||||
self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
|
||||
},
|
||||
|
||||
init: function () {
|
||||
var self = this;
|
||||
self.setSelectors();
|
||||
self.editButton.on('click', function (e) {
|
||||
self.edit();
|
||||
});
|
||||
self.closeButton.on('click', function (e) {
|
||||
self.view();
|
||||
});
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
this.viewFields.hide();
|
||||
this.editButton.hide();
|
||||
this.deleteButton.hide();
|
||||
this.closeButton.show();
|
||||
this.editFields.show();
|
||||
cmInstance.refresh();
|
||||
},
|
||||
|
||||
view: function (event) {
|
||||
this.editButton.show();
|
||||
this.deleteButton.show();
|
||||
this.editFields.hide();
|
||||
this.closeButton.hide();
|
||||
this.viewFields.show();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* OnLine presence using channelstream
|
||||
|
|
@ -813,29 +1105,29 @@ window.refreshComments = function (version) {
|
|||
$.each($('.comment'), function (idx, element) {
|
||||
currentIDs.push($(element).data('commentId'));
|
||||
});
|
||||
var data = {"comments[]": currentIDs};
|
||||
var data = {"comments": currentIDs};
|
||||
|
||||
var $targetElem = $('.comments-content-table');
|
||||
$targetElem.css('opacity', 0.3);
|
||||
$targetElem.load(
|
||||
loadUrl, data, function (responseText, textStatus, jqXHR) {
|
||||
if (jqXHR.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
var $counterElem = $('#comments-count');
|
||||
var newCount = $(responseText).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
tooltipActivate();
|
||||
var success = function (data) {
|
||||
var $counterElem = $('#comments-count');
|
||||
var newCount = $(data).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
);
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
$targetElem.html(data);
|
||||
tooltipActivate();
|
||||
}
|
||||
|
||||
ajaxPOST(loadUrl, data, success, null, {})
|
||||
|
||||
}
|
||||
|
||||
window.refreshTODOs = function (version) {
|
||||
|
|
@ -858,28 +1150,28 @@ window.refreshTODOs = function (version) {
|
|||
currentIDs.push($(element).data('commentId'));
|
||||
});
|
||||
|
||||
var data = {"comments[]": currentIDs};
|
||||
var data = {"comments": currentIDs};
|
||||
var $targetElem = $('.todos-content-table');
|
||||
$targetElem.css('opacity', 0.3);
|
||||
$targetElem.load(
|
||||
loadUrl, data, function (responseText, textStatus, jqXHR) {
|
||||
if (jqXHR.status !== 200) {
|
||||
return false;
|
||||
}
|
||||
var $counterElem = $('#todos-count')
|
||||
var newCount = $(responseText).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
tooltipActivate();
|
||||
var success = function (data) {
|
||||
var $counterElem = $('#todos-count')
|
||||
var newCount = $(data).data('counter');
|
||||
if (newCount !== undefined) {
|
||||
var callback = function () {
|
||||
$counterElem.animate({'opacity': 1.00}, 200)
|
||||
$counterElem.html(newCount);
|
||||
};
|
||||
$counterElem.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
);
|
||||
|
||||
$targetElem.css('opacity', 1);
|
||||
$targetElem.html(data);
|
||||
tooltipActivate();
|
||||
}
|
||||
|
||||
ajaxPOST(loadUrl, data, success, null, {})
|
||||
|
||||
}
|
||||
|
||||
window.refreshAllComments = function (version) {
|
||||
|
|
@ -888,3 +1180,12 @@ window.refreshAllComments = function (version) {
|
|||
refreshComments(version);
|
||||
refreshTODOs(version);
|
||||
};
|
||||
|
||||
window.sidebarComment = function (commentId) {
|
||||
var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
|
||||
if (!jsonData) {
|
||||
return 'Failed to load comment {0}'.format(commentId)
|
||||
}
|
||||
var funcData = JSON.parse(atob(jsonData));
|
||||
return renderTemplate('sideBarCommentHovercard', funcData)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,15 +57,18 @@ var ajaxGET = function (url, success, failure) {
|
|||
return request;
|
||||
};
|
||||
|
||||
var ajaxPOST = function (url, postData, success, failure) {
|
||||
var sUrl = url;
|
||||
var postData = toQueryString(postData);
|
||||
var request = $.ajax({
|
||||
var ajaxPOST = function (url, postData, success, failure, options) {
|
||||
|
||||
var ajaxSettings = $.extend({
|
||||
type: 'POST',
|
||||
url: sUrl,
|
||||
data: postData,
|
||||
url: url,
|
||||
data: toQueryString(postData),
|
||||
headers: {'X-PARTIAL-XHR': true}
|
||||
})
|
||||
}, options);
|
||||
|
||||
var request = $.ajax(
|
||||
ajaxSettings
|
||||
)
|
||||
.done(function (data) {
|
||||
success(data);
|
||||
})
|
||||
|
|
@ -126,7 +129,8 @@ function formatErrorMessage(jqXHR, textStatus, errorThrown, prefix) {
|
|||
} else if (errorThrown === 'abort') {
|
||||
return (prefix + 'Ajax request aborted.');
|
||||
} else {
|
||||
return (prefix + 'Uncaught Error.\n' + jqXHR.responseText);
|
||||
var errInfo = 'Uncaught Error. code: {0}\n'.format(jqXHR.status)
|
||||
return (prefix + errInfo + jqXHR.responseText);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
<h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
|
||||
</div>
|
||||
<div class="panel-body panel-body-min-height">
|
||||
<table id="pull_request_list_table" class="display"></table>
|
||||
<table id="pull_request_list_table" class="rctable table-bordered"></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -58,14 +58,8 @@
|
|||
|
||||
dom: 'rtp',
|
||||
pageLength: ${c.visual.dashboard_items},
|
||||
order: [[2, "desc"]],
|
||||
order: [[1, "desc"]],
|
||||
columns: [
|
||||
{
|
||||
data: {
|
||||
"_": "target_repo",
|
||||
"sort": "target_repo"
|
||||
}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false
|
||||
},
|
||||
{
|
||||
data: {
|
||||
"_": "status",
|
||||
|
|
@ -101,7 +95,13 @@
|
|||
"_": "updated_on",
|
||||
"sort": "updated_on_raw"
|
||||
}, title: "${_('Last Update')}", className: "td-time"
|
||||
}
|
||||
},
|
||||
{
|
||||
data: {
|
||||
"_": "target_repo",
|
||||
"sort": "target_repo"
|
||||
}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false
|
||||
},
|
||||
],
|
||||
language: {
|
||||
paginate: DEFAULT_GRID_PAGINATION,
|
||||
|
|
|
|||
|
|
@ -785,15 +785,15 @@
|
|||
|
||||
- Prefix query to allow special search:
|
||||
|
||||
user:admin, to search for usernames, always global
|
||||
<strong>user:</strong>admin, to search for usernames, always global
|
||||
|
||||
user_group:devops, to search for user groups, always global
|
||||
<strong>user_group:</strong>devops, to search for user groups, always global
|
||||
|
||||
pr:303, to search for pull request number, title, or description, always global
|
||||
<strong>pr:</strong>303, to search for pull request number, title, or description, always global
|
||||
|
||||
commit:efced4, to search for commits, scoped to repositories or groups
|
||||
<strong>commit:</strong>efced4, to search for commits, scoped to repositories or groups
|
||||
|
||||
file:models.py, to search for file paths, scoped to repositories or groups
|
||||
<strong>file:</strong>models.py, to search for file paths, scoped to repositories or groups
|
||||
|
||||
% if c.template_context['search_context']['repo_id']:
|
||||
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 @@ examples = [
|
|||
),
|
||||
|
||||
(
|
||||
'Redmine',
|
||||
'(^#|\s#)(?P<issue_id>\d+)',
|
||||
'Tickets with #123 (Redmine etc)',
|
||||
'(?<![a-zA-Z0-9_/]{1,10}-?)(#)(?P<issue_id>\d+)',
|
||||
'https://myissueserver.com/${repo}/issue/${issue_id}',
|
||||
''
|
||||
),
|
||||
|
|
@ -38,14 +38,15 @@ examples = [
|
|||
|
||||
(
|
||||
'JIRA - All tickets',
|
||||
'(^|\s\w+-\d+)',
|
||||
'https://myjira.com/browse/${id}',
|
||||
# official JIRA ticket pattern
|
||||
'(?<![a-zA-Z0-9_/#]-?)(?P<issue_id>[A-Z]{1,6}-(?:[1-9][0-9]{0,7}))',
|
||||
'https://myjira.com/browse/${issue_id}',
|
||||
''
|
||||
),
|
||||
|
||||
(
|
||||
'JIRA - Project (JRA)',
|
||||
'(?:(^|\s)(?P<issue_id>(?:JRA-|JRA-)(?:\d+)))',
|
||||
'JIRA - Single project (JRA-XXXXXXXX)',
|
||||
'(?<![a-zA-Z0-9_/#]-?)(?P<issue_id>JRA-(?:[1-9][0-9]{0,7}))',
|
||||
'https://myjira.com/${issue_id}',
|
||||
''
|
||||
),
|
||||
|
|
@ -275,13 +276,19 @@ examples = [
|
|||
<div class='textarea-full'>
|
||||
<textarea id="test_pattern_data" rows="12">
|
||||
This is an example text for testing issue tracker patterns.
|
||||
This commit fixes ticket #451 and ticket #910.
|
||||
Following tickets will get mentioned:
|
||||
This commit fixes ticket #451 and ticket #910, reference for JRA-401.
|
||||
The following tickets will get mentioned:
|
||||
#123
|
||||
#456
|
||||
JRA-123
|
||||
JRA-456
|
||||
Open a pull request !101 to contribute !
|
||||
#456 and PROJ-101
|
||||
JRA-123 and #123
|
||||
PROJ-456
|
||||
|
||||
[my artifact](http://something.com/JRA-1234-build.zip)
|
||||
|
||||
- #1001
|
||||
- JRA-998
|
||||
|
||||
Open a pull request !101 to contribute!
|
||||
Added tag v1.3.0 for commit 0f3b629be725
|
||||
|
||||
Add a test pattern here and hit preview to see the link.
|
||||
|
|
|
|||
|
|
@ -89,36 +89,41 @@
|
|||
if is_pr:
|
||||
version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version')
|
||||
%>
|
||||
|
||||
<script type="text/javascript">
|
||||
// closure function helper
|
||||
var sidebarComment${comment_obj.comment_id} = function() {
|
||||
return renderTemplate('sideBarCommentHovercard', {
|
||||
version_info: "${version_info}",
|
||||
file_name: "${comment_obj.f_path}",
|
||||
line_no: "${comment_obj.line_no}",
|
||||
outdated: ${h.json.dumps(comment_obj.outdated)},
|
||||
inline: ${h.json.dumps(comment_obj.is_inline)},
|
||||
is_todo: ${h.json.dumps(comment_obj.is_todo)},
|
||||
created_on: "${h.format_date(comment_obj.created_on)}",
|
||||
datetime: "${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}",
|
||||
review_status: "${(comment_obj.review_status or '')}"
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
% if comment_obj.outdated:
|
||||
<i class="icon-comment-toggle tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% elif comment_obj.is_inline:
|
||||
<i class="icon-code tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% else:
|
||||
<i class="icon-comment tooltip-hovercard" data-hovercard-url="javascript:sidebarComment${comment_obj.comment_id}()"></i>
|
||||
% endif
|
||||
|
||||
## NEW, since refresh
|
||||
## new comments, since refresh
|
||||
% if existing_ids and comment_obj.comment_id not in existing_ids:
|
||||
<span class="tag">NEW</span>
|
||||
<div class="tooltip" style="position: absolute; left: 8px; color: #682668" title="New comment">
|
||||
!
|
||||
</div>
|
||||
% endif
|
||||
|
||||
<%
|
||||
data = h.json.dumps({
|
||||
'comment_id': comment_obj.comment_id,
|
||||
'version_info': version_info,
|
||||
'file_name': comment_obj.f_path,
|
||||
'line_no': comment_obj.line_no,
|
||||
'outdated': comment_obj.outdated,
|
||||
'inline': comment_obj.is_inline,
|
||||
'is_todo': comment_obj.is_todo,
|
||||
'created_on': h.format_date(comment_obj.created_on),
|
||||
'datetime': '{}{}'.format(comment_obj.created_on, h.get_timezone(comment_obj.created_on, time_is_local=True)),
|
||||
'review_status': (comment_obj.review_status or '')
|
||||
})
|
||||
|
||||
if comment_obj.outdated:
|
||||
icon = 'icon-comment-toggle'
|
||||
elif comment_obj.is_inline:
|
||||
icon = 'icon-code'
|
||||
else:
|
||||
icon = 'icon-comment'
|
||||
%>
|
||||
|
||||
<i id="commentHovercard${comment_obj.comment_id}"
|
||||
class="${icon} tooltip-hovercard"
|
||||
data-hovercard-url="javascript:sidebarComment(${comment_obj.comment_id})"
|
||||
data-comment-json-b64='${h.b64(data)}'>
|
||||
</i>
|
||||
|
||||
</td>
|
||||
|
||||
<td class="td-todo-gravatar">
|
||||
|
|
|
|||
|
|
@ -30,11 +30,21 @@
|
|||
</ul>
|
||||
%endif
|
||||
%if c.has_references:
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
<span id="obj_count">0</span> ${_('bookmarks')}
|
||||
<div class="grid-quick-filter">
|
||||
<ul class="grid-filter-box">
|
||||
<li class="grid-filter-box-icon">
|
||||
<i class="icon-search"></i>
|
||||
</li>
|
||||
<li class="grid-filter-box-input">
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="obj_count">0</div>
|
||||
%endif
|
||||
</div>
|
||||
<table id="obj_list_table" class="display"></table>
|
||||
|
||||
<table id="obj_list_table" class="rctable table-bordered"></table>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -43,7 +53,9 @@ $(document).ready(function() {
|
|||
|
||||
var get_datatable_count = function(){
|
||||
var api = $('#obj_list_table').dataTable().api();
|
||||
$('#obj_count').text(api.page.info().recordsDisplay);
|
||||
var total = api.page.info().recordsDisplay
|
||||
var _text = _ngettext('{0} bookmark', '{0} bookmarks', total).format(total);
|
||||
$('#obj_count').text(_text);
|
||||
};
|
||||
|
||||
// object list
|
||||
|
|
|
|||
|
|
@ -30,11 +30,20 @@
|
|||
</ul>
|
||||
%endif
|
||||
%if c.has_references:
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
<span id="obj_count">0</span> ${_('branches')}
|
||||
<div class="grid-quick-filter">
|
||||
<ul class="grid-filter-box">
|
||||
<li class="grid-filter-box-icon">
|
||||
<i class="icon-search"></i>
|
||||
</li>
|
||||
<li class="grid-filter-box-input">
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="obj_count">0</div>
|
||||
%endif
|
||||
</div>
|
||||
<table id="obj_list_table" class="display"></table>
|
||||
<table id="obj_list_table" class="rctable table-bordered"></table>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
|
@ -42,7 +51,10 @@ $(document).ready(function() {
|
|||
|
||||
var get_datatable_count = function(){
|
||||
var api = $('#obj_list_table').dataTable().api();
|
||||
$('#obj_count').text(api.page.info().recordsDisplay);
|
||||
var total = api.page.info().recordsDisplay
|
||||
var _text = _ngettext('{0} branch', '{0} branches', total).format(total);
|
||||
|
||||
$('#obj_count').text(_text);
|
||||
};
|
||||
|
||||
// object list
|
||||
|
|
|
|||
|
|
@ -187,12 +187,12 @@
|
|||
<div class="sidebar-element clear-both">
|
||||
<% vote_title = _ungettext(
|
||||
'Status calculated based on votes from {} reviewer',
|
||||
'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
|
||||
'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
|
||||
%>
|
||||
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.commit_review_status}"></i>
|
||||
${len(c.allowed_reviewers)}
|
||||
${c.reviewers_count}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -420,7 +420,8 @@
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
ReviewersPanel.init(null, setReviewersData);
|
||||
reviewersController = new ReviewersController();
|
||||
ReviewersPanel.init(reviewersController, null, setReviewersData);
|
||||
|
||||
var channel = '${c.commit_broadcast_channel}';
|
||||
new ReviewerPresenceController(channel)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@
|
|||
<div id="graph_content" class="graph_full_width">
|
||||
|
||||
<div class="table">
|
||||
<table id="changesets" class="rctable">
|
||||
<table id="changesets" class="rctable table-bordered">
|
||||
<tr>
|
||||
## checkbox
|
||||
<th colspan="4">
|
||||
|
|
|
|||
|
|
@ -379,22 +379,22 @@ ${h.style_metatag(tag_type, tag)|n,trim}
|
|||
</%def>
|
||||
|
||||
<%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
|
||||
<code>
|
||||
<a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
|
||||
|
||||
% if short:
|
||||
!${pull_request_id}
|
||||
% else:
|
||||
${_('Pull request !{}').format(pull_request_id)}
|
||||
% endif
|
||||
|
||||
% if state not in ['created']:
|
||||
<span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
|
||||
% endif
|
||||
|
||||
% if is_wip:
|
||||
<span class="tag tooltip" title="${_('Work in progress')}">wip</span>
|
||||
% endif
|
||||
</a>
|
||||
</code>
|
||||
% if state not in ['created']:
|
||||
<span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
|
||||
% endif
|
||||
|
||||
% if is_wip:
|
||||
<span class="tag tooltip" title="${_('Work in progress')}">wip</span>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="pullrequest_updated_on(updated_on)">
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@
|
|||
<span class="user"> <a href="/_profiles/jenkins-tests">jenkins-tests</a> (reviewer)</span>
|
||||
</div>
|
||||
<input id="reviewer_70_input" type="hidden" value="70" name="review_members">
|
||||
<div class="reviewer_member_remove action_button" onclick="removeReviewMember(70, true)" style="visibility: hidden;">
|
||||
<div class="reviewer_member_remove action_button" onclick="removeMember(70, true)" style="visibility: hidden;">
|
||||
<i class="icon-remove"></i>
|
||||
</div>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -66,9 +66,18 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
<tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
|
||||
|
||||
<td style="width: 20px">
|
||||
<div class="tooltip presence-state" style="display: none; position: absolute; left: 2px" title="This users is currently at this page">
|
||||
<i class="icon-eye" style="color: #0ac878"></i>
|
||||
</div>
|
||||
<% if (role === 'reviewer') { %>
|
||||
<div class="reviewer_status tooltip" title="<%= review_status_label %>">
|
||||
<i class="icon-circle review-status-<%= review_status %>"></i>
|
||||
</div>
|
||||
<% } else if (role === 'observer') { %>
|
||||
<div class="tooltip" title="Observer without voting right.">
|
||||
<i class="icon-circle-thin"></i>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
|
@ -84,9 +93,6 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
'gravatar_url': member.gravatar_link
|
||||
})
|
||||
%>
|
||||
<span class="tooltip presence-state" style="display: none" title="This users is currently at this page">
|
||||
<i class="icon-eye" style="color: #0ac878"></i>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
|
|
@ -108,7 +114,7 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
<% } else { %>
|
||||
<td style="text-align: right;width: 10px;">
|
||||
<% if (allowed_to_update) { %>
|
||||
<div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
|
||||
<div class="<%=role %>_member_remove" onclick="reviewersController.removeMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
|
||||
<i class="icon-remove"></i>
|
||||
</div>
|
||||
<% } %>
|
||||
|
|
@ -117,7 +123,7 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<tr id="reviewer_<%= member.user_id %>_rules">
|
||||
<td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
|
||||
<input type="hidden" name="__start__" value="reviewer:mapping">
|
||||
|
||||
|
|
@ -149,6 +155,7 @@ var data_hovercard_url = pyroutes.url('hovercard_user', {"user_id": user_id})
|
|||
|
||||
<input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
|
||||
<input type="hidden" name="mandatory" value="<%= mandatory %>"/>
|
||||
<input type="hidden" name="role" value="<%= role %>"/>
|
||||
|
||||
<input type="hidden" name="__end__" value="reviewer:mapping">
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ data = {
|
|||
'pr_title': pull_request.title,
|
||||
}
|
||||
|
||||
subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"')
|
||||
if user_role == 'observer':
|
||||
subject_template = email_pr_review_subject_template or _('{user} added you as observer to pull request. !{pr_id}: "{pr_title}"')
|
||||
else:
|
||||
subject_template = email_pr_review_subject_template or _('{user} requested a pull request review. !{pr_id}: "{pr_title}"')
|
||||
%>
|
||||
|
||||
${subject_template.format(**data) |n}
|
||||
|
|
@ -34,6 +37,7 @@ data = {
|
|||
'source_repo_url': pull_request_source_repo_url,
|
||||
'target_repo_url': pull_request_target_repo_url,
|
||||
}
|
||||
|
||||
%>
|
||||
|
||||
* ${_('Pull Request link')}: ${pull_request_url}
|
||||
|
|
@ -51,7 +55,7 @@ ${pull_request.description | trim}
|
|||
|
||||
% for commit_id, message in pull_request_commits:
|
||||
- ${h.short_id(commit_id)}
|
||||
${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
|
||||
${h.chop_at_smart(message.lstrip(), '\n', suffix_if_chopped='...')}
|
||||
|
||||
% endfor
|
||||
|
||||
|
|
@ -78,19 +82,23 @@ data = {
|
|||
<table style="text-align:left;vertical-align:middle;width: 100%">
|
||||
<tr>
|
||||
<td style="width:100%;border-bottom:1px solid #dbd9da;">
|
||||
|
||||
<div style="margin: 0; font-weight: bold">
|
||||
% if user_role == 'observer':
|
||||
<div class="clear-both" class="clear-both" style="margin-bottom: 4px">
|
||||
<span style="color:#7E7F7F">@${h.person(user.username)}</span>
|
||||
${_('added you as observer to')}
|
||||
<a href="${pull_request_url}" style="${base.link_css()}">pull request</a>.
|
||||
</div>
|
||||
% else:
|
||||
<div class="clear-both" class="clear-both" style="margin-bottom: 4px">
|
||||
<span style="color:#7E7F7F">@${h.person(user.username)}</span>
|
||||
${_('requested a')}
|
||||
<a href="${pull_request_url}" style="${base.link_css()}">
|
||||
${_('pull request review.').format(**data) }
|
||||
</a>
|
||||
<a href="${pull_request_url}" style="${base.link_css()}">pull request</a> review.
|
||||
</div>
|
||||
% endif
|
||||
<div style="margin-top: 10px"></div>
|
||||
${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
default_landing_ref = c.commit.raw_id
|
||||
%>
|
||||
<div id="file-tree-wrapper" class="browser-body ${('full-load' if c.full_load else '')}">
|
||||
<table class="code-browser rctable repo_summary">
|
||||
<table class="code-browser rctable table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${_('Name')}</th>
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@
|
|||
## REVIEWERS
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="pullrequest_reviewers">${_('Reviewers')}:</label>
|
||||
<label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
|
||||
</div>
|
||||
<div class="content">
|
||||
## REVIEW RULES
|
||||
|
|
@ -125,29 +125,84 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
## REVIEWERS
|
||||
## REVIEWERS / OBSERVERS
|
||||
<div class="reviewers-title">
|
||||
<div class="pr-details-title">
|
||||
${_('Pull request reviewers')}
|
||||
<span class="calculate-reviewers"> - ${_('loading...')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reviewers" class="pr-details-content reviewers">
|
||||
## members goes here, filled via JS based on initial selection !
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
<table id="review_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel
|
||||
</table>
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
|
||||
<div id="add_reviewer_input" class='ac'>
|
||||
<div class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
<ul class="nav-links clearfix">
|
||||
|
||||
## TAB1 MANDATORY REVIEWERS
|
||||
<li class="active">
|
||||
<a id="reviewers-btn" href="#showReviewers" tabindex="-1">
|
||||
Reviewers
|
||||
<span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
## TAB2 OBSERVERS
|
||||
<li class="">
|
||||
<a id="observers-btn" href="#showObservers" tabindex="-1">
|
||||
Observers
|
||||
<span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
## TAB1 MANDATORY REVIEWERS
|
||||
<div id="reviewers-container">
|
||||
<span class="calculate-reviewers">
|
||||
<h4>${_('loading...')}</h4>
|
||||
</span>
|
||||
|
||||
<div id="reviewers" class="pr-details-content reviewers">
|
||||
## members goes here, filled via JS based on initial selection !
|
||||
<input type="hidden" name="__start__" value="review_members:sequence">
|
||||
<table id="review_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
|
||||
</table>
|
||||
<input type="hidden" name="__end__" value="review_members:sequence">
|
||||
|
||||
<div id="add_reviewer_input" class='ac'>
|
||||
<div class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## TAB2 OBSERVERS
|
||||
<div id="observers-container" style="display: none">
|
||||
<span class="calculate-reviewers">
|
||||
<h4>${_('loading...')}</h4>
|
||||
</span>
|
||||
% if c.rhodecode_edition_id == 'EE':
|
||||
<div id="observers" class="pr-details-content observers">
|
||||
## members goes here, filled via JS based on initial selection !
|
||||
<input type="hidden" name="__start__" value="observer_members:sequence">
|
||||
<table id="observer_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
|
||||
</table>
|
||||
<input type="hidden" name="__end__" value="observer_members:sequence">
|
||||
|
||||
<div id="add_observer_input" class='ac'>
|
||||
<div class="observer_ac">
|
||||
${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
|
||||
<div id="observers_container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% else:
|
||||
<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>
|
||||
<p>
|
||||
Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes.
|
||||
</p>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -248,12 +303,19 @@
|
|||
|
||||
var originalOption = data.element;
|
||||
return prefix + escapeMarkup(data.text);
|
||||
};formatSelection:
|
||||
};
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
|
||||
|
||||
var diffDataHandler = function(data) {
|
||||
if (data['error'] !== undefined) {
|
||||
var noCommitsMsg = '<span class="alert-text-error">{0}</span>'.format(data['error']);
|
||||
prButtonLock(true, noCommitsMsg, 'compare');
|
||||
//make both panels equal
|
||||
$('.target-panel').height($('.source-panel').height())
|
||||
return false
|
||||
}
|
||||
|
||||
var commitElements = data['commits'];
|
||||
var files = data['files'];
|
||||
|
|
@ -307,8 +369,10 @@
|
|||
var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
|
||||
msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
|
||||
|
||||
|
||||
$.each(commitElements, function(idx, value) {
|
||||
msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]);
|
||||
var commit_id = value["commit_id"]
|
||||
msg += '<input type="hidden" name="revisions" value="{0}">'.format(commit_id);
|
||||
});
|
||||
|
||||
msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
|
||||
|
|
@ -338,8 +402,8 @@
|
|||
}
|
||||
|
||||
//make both panels equal
|
||||
$('.target-panel').height($('.source-panel').height())
|
||||
|
||||
$('.target-panel').height($('.source-panel').height());
|
||||
return true
|
||||
};
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
|
|
@ -465,8 +529,7 @@
|
|||
queryTargetRefs(initialData, query)
|
||||
},
|
||||
initSelection: initRefSelection()
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
var sourceRepoSelect2 = Select2Box($sourceRepo, {
|
||||
query: function(query) {}
|
||||
|
|
@ -521,12 +584,12 @@
|
|||
|
||||
});
|
||||
|
||||
$pullRequestForm.on('submit', function(e){
|
||||
// Flush changes into textarea
|
||||
codeMirrorInstance.save();
|
||||
prButtonLock(true, null, 'all');
|
||||
$pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
|
||||
});
|
||||
$pullRequestForm.on('submit', function(e){
|
||||
// Flush changes into textarea
|
||||
codeMirrorInstance.save();
|
||||
prButtonLock(true, null, 'all');
|
||||
$pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
|
||||
});
|
||||
|
||||
prButtonLock(true, "${_('Please select source and target')}", 'all');
|
||||
|
||||
|
|
@ -543,12 +606,44 @@
|
|||
$sourceRef.select2('val', '${c.default_source_ref}');
|
||||
|
||||
|
||||
// default reviewers
|
||||
// default reviewers / observers
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
% endif
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
ReviewerAutoComplete('#user', reviewersController);
|
||||
ObserverAutoComplete('#observer', reviewersController);
|
||||
|
||||
// TODO, move this to another handler
|
||||
|
||||
var $reviewersBtn = $('#reviewers-btn');
|
||||
var $reviewersContainer = $('#reviewers-container');
|
||||
|
||||
var $observersBtn = $('#observers-btn')
|
||||
var $observersContainer = $('#observers-container');
|
||||
|
||||
$reviewersBtn.on('click', function (e) {
|
||||
|
||||
$observersContainer.hide();
|
||||
$reviewersContainer.show();
|
||||
|
||||
$observersBtn.parent().removeClass('active');
|
||||
$reviewersBtn.parent().addClass('active');
|
||||
e.preventDefault();
|
||||
|
||||
})
|
||||
|
||||
$observersBtn.on('click', function (e) {
|
||||
|
||||
$reviewersContainer.hide();
|
||||
$observersContainer.show();
|
||||
|
||||
$reviewersBtn.parent().removeClass('active');
|
||||
$observersBtn.parent().addClass('active');
|
||||
e.preventDefault();
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -556,12 +556,12 @@
|
|||
<div class="sidebar-element clear-both">
|
||||
<% vote_title = _ungettext(
|
||||
'Status calculated based on votes from {} reviewer',
|
||||
'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
|
||||
'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
|
||||
%>
|
||||
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
|
||||
<i class="icon-circle review-status-${c.pull_request_review_status}"></i>
|
||||
${len(c.allowed_reviewers)}
|
||||
${c.reviewers_count}
|
||||
</div>
|
||||
|
||||
## REVIEW RULES
|
||||
|
|
@ -609,13 +609,13 @@
|
|||
<div id="add_reviewer" class="ac" style="display: none;">
|
||||
%if c.allowed_to_update:
|
||||
% if not c.forbid_adding_reviewers:
|
||||
<div id="add_reviewer_input" class="reviewer_ac">
|
||||
${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
|
||||
<div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
|
||||
<input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
|
||||
<div id="reviewers_container"></div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="pull-right">
|
||||
<button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
|
||||
<div class="pull-right" style="margin-bottom: 15px">
|
||||
<button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
|
|
@ -623,23 +623,59 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
## ## OBSERVERS
|
||||
## <div class="sidebar-element clear-both">
|
||||
## <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
|
||||
## <i class="icon-eye"></i>
|
||||
## 0
|
||||
## </div>
|
||||
##
|
||||
## <div class="right-sidebar-expanded-state pr-details-title">
|
||||
## <span class="sidebar-heading">
|
||||
## <i class="icon-eye"></i>
|
||||
## ${_('Observers')}
|
||||
## </span>
|
||||
## </div>
|
||||
## <div class="right-sidebar-expanded-state pr-details-content">
|
||||
## No observers
|
||||
## </div>
|
||||
## </div>
|
||||
## OBSERVERS
|
||||
% if c.rhodecode_edition_id == 'EE':
|
||||
<div class="sidebar-element clear-both">
|
||||
<% vote_title = _ungettext(
|
||||
'{} observer without voting right.',
|
||||
'{} observers without voting right.', c.observers_count).format(c.observers_count)
|
||||
%>
|
||||
|
||||
<div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
|
||||
<i class="icon-circle-thin"></i>
|
||||
${c.observers_count}
|
||||
</div>
|
||||
|
||||
<div class="right-sidebar-expanded-state pr-details-title">
|
||||
<span class="tooltip sidebar-heading" title="${vote_title}">
|
||||
<i class="icon-circle-thin"></i>
|
||||
${_('Observers')}
|
||||
</span>
|
||||
%if c.allowed_to_update:
|
||||
<span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
|
||||
<span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
<div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
|
||||
## members redering block
|
||||
<input type="hidden" name="__start__" value="observer_members:sequence">
|
||||
|
||||
<table id="observer_members" class="group_members">
|
||||
## This content is loaded via JS and ReviewersPanel
|
||||
</table>
|
||||
|
||||
<input type="hidden" name="__end__" value="observer_members:sequence">
|
||||
## end members redering block
|
||||
|
||||
%if not c.pull_request.is_closed():
|
||||
<div id="add_observer" class="ac" style="display: none;">
|
||||
%if c.allowed_to_update:
|
||||
% if not c.forbid_adding_reviewers or 1:
|
||||
<div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
|
||||
<input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
|
||||
<div id="observers_container"></div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="pull-right" style="margin-bottom: 15px">
|
||||
<button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
## TODOs
|
||||
<div class="sidebar-element clear-both">
|
||||
|
|
@ -757,7 +793,7 @@
|
|||
|
||||
<tr><td><code>${_('In pull request description')}:</code></td></tr>
|
||||
% if c.referenced_desc_issues:
|
||||
% for ticket_dict in c.referenced_desc_issues:
|
||||
% for ticket_dict in sorted(c.referenced_desc_issues):
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${ticket_dict.get('url')}">
|
||||
|
|
@ -776,7 +812,7 @@
|
|||
|
||||
<tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
|
||||
% if c.referenced_commit_issues:
|
||||
% for ticket_dict in c.referenced_commit_issues:
|
||||
% for ticket_dict in sorted(c.referenced_commit_issues):
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${ticket_dict.get('url')}">
|
||||
|
|
@ -815,6 +851,7 @@ updateController = new UpdatePrController();
|
|||
|
||||
window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
|
||||
window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
|
||||
window.setObserversData = ${c.pull_request_set_observers_data_json | n};
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
|
@ -822,44 +859,9 @@ window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
|
|||
// custom code mirror
|
||||
var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
|
||||
var PRDetails = {
|
||||
editButton: $('#open_edit_pullrequest'),
|
||||
closeButton: $('#close_edit_pullrequest'),
|
||||
deleteButton: $('#delete_pullrequest'),
|
||||
viewFields: $('#pr-desc, #pr-title'),
|
||||
editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
|
||||
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.editButton.on('click', function (e) {
|
||||
that.edit();
|
||||
});
|
||||
this.closeButton.on('click', function (e) {
|
||||
that.view();
|
||||
});
|
||||
},
|
||||
|
||||
edit: function (event) {
|
||||
var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
this.viewFields.hide();
|
||||
this.editButton.hide();
|
||||
this.deleteButton.hide();
|
||||
this.closeButton.show();
|
||||
this.editFields.show();
|
||||
cmInstance.refresh();
|
||||
},
|
||||
|
||||
view: function (event) {
|
||||
this.editButton.show();
|
||||
this.deleteButton.show();
|
||||
this.editFields.hide();
|
||||
this.closeButton.hide();
|
||||
this.viewFields.show();
|
||||
}
|
||||
};
|
||||
|
||||
PRDetails.init();
|
||||
ReviewersPanel.init(reviewerRulesData, setReviewersData);
|
||||
ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
|
||||
ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
|
||||
|
||||
window.showOutdated = function (self) {
|
||||
$('.comment-inline.comment-outdated').show();
|
||||
|
|
@ -929,12 +931,17 @@ window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
|
|||
title, description, renderer);
|
||||
});
|
||||
|
||||
$('#update_pull_request').on('click', function (e) {
|
||||
$(this).attr('disabled', 'disabled');
|
||||
$(this).addClass('disabled');
|
||||
$(this).html(_gettext('Saving...'));
|
||||
var $updateButtons = $('#update_reviewers,#update_observers');
|
||||
$updateButtons.on('click', function (e) {
|
||||
var role = $(this).data('role');
|
||||
$updateButtons.attr('disabled', 'disabled');
|
||||
$updateButtons.addClass('disabled');
|
||||
$updateButtons.html(_gettext('Saving...'));
|
||||
reviewersController.updateReviewers(
|
||||
"${c.repo_name}", "${c.pull_request.pull_request_id}");
|
||||
templateContext.repo_name,
|
||||
templateContext.pull_request_data.pull_request_id,
|
||||
role
|
||||
);
|
||||
});
|
||||
|
||||
// fixing issue with caches on firefox
|
||||
|
|
@ -978,7 +985,8 @@ window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
|
|||
refreshMergeChecks();
|
||||
};
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
ReviewerAutoComplete('#user', reviewersController);
|
||||
ObserverAutoComplete('#observer', reviewersController);
|
||||
|
||||
})();
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
|
||||
<div class="main-content-full-width">
|
||||
<table id="pull_request_list_table" class="display"></table>
|
||||
<table id="pull_request_list_table" class="rctable table-bordered"></table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
%if c.repo_commits:
|
||||
<table class="rctable repo_summary table_disp">
|
||||
<table class="rctable table-bordered">
|
||||
<tr>
|
||||
|
||||
<th class="status"></th>
|
||||
<th>${_('Commit')}</th>
|
||||
<th></th>
|
||||
<th>${_('Commit message')}</th>
|
||||
<th>${_('Age')}</th>
|
||||
<th>${_('Author')}</th>
|
||||
|
|
|
|||
|
|
@ -30,11 +30,20 @@
|
|||
</ul>
|
||||
%endif
|
||||
%if c.has_references:
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
<span id="obj_count">0</span> ${_('tags')}
|
||||
<div class="grid-quick-filter">
|
||||
<ul class="grid-filter-box">
|
||||
<li class="grid-filter-box-icon">
|
||||
<i class="icon-search"></i>
|
||||
</li>
|
||||
<li class="grid-filter-box-input">
|
||||
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="obj_count">0</div>
|
||||
%endif
|
||||
</div>
|
||||
<table id="obj_list_table" class="display"></table>
|
||||
<table id="obj_list_table" class="rctable table-bordered"></table>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
@ -43,7 +52,10 @@ $(document).ready(function() {
|
|||
|
||||
var get_datatable_count = function(){
|
||||
var api = $('#obj_list_table').dataTable().api();
|
||||
$('#obj_count').text(api.page.info().recordsDisplay);
|
||||
var total = api.page.info().recordsDisplay
|
||||
var _text = _ngettext('{0} tag', '{0} tags', total).format(total);
|
||||
|
||||
$('#obj_count').text(_text);
|
||||
};
|
||||
|
||||
// object list
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ def test_extract_issues(backend, text_string, pattern, expected):
|
|||
|
||||
with mock.patch.object(IssueTrackerSettingsModel,
|
||||
'get_settings', get_settings_mock):
|
||||
text, issues = helpers.process_patterns(text_string, repo.repo_name)
|
||||
text, issues, errors = helpers.process_patterns(text_string, repo.repo_name)
|
||||
|
||||
expected = copy.deepcopy(expected)
|
||||
for item in expected:
|
||||
|
|
@ -159,7 +159,7 @@ def test_process_patterns_repo(backend, text_string, pattern, expected_text, lin
|
|||
|
||||
with mock.patch.object(IssueTrackerSettingsModel,
|
||||
'get_settings', get_settings_mock):
|
||||
processed_text, issues = helpers.process_patterns(
|
||||
processed_text, issues, error = helpers.process_patterns(
|
||||
text_string, repo.repo_name, link_format)
|
||||
|
||||
assert processed_text == expected_text.format(repo=repo.repo_name)
|
||||
|
|
@ -186,7 +186,7 @@ def test_process_patterns_no_repo(text_string, pattern, expected_text):
|
|||
|
||||
with mock.patch.object(IssueTrackerSettingsModel,
|
||||
'get_global_settings', get_settings_mock):
|
||||
processed_text, issues = helpers.process_patterns(
|
||||
processed_text, issues, errors = helpers.process_patterns(
|
||||
text_string, '')
|
||||
|
||||
assert processed_text == expected_text
|
||||
|
|
@ -211,7 +211,7 @@ def test_process_patterns_non_existent_repo_name(backend):
|
|||
|
||||
with mock.patch.object(IssueTrackerSettingsModel,
|
||||
'get_global_settings', get_settings_mock):
|
||||
processed_text, issues = helpers.process_patterns(
|
||||
processed_text, issues, errors = helpers.process_patterns(
|
||||
text_string, 'do-not-exist')
|
||||
|
||||
assert processed_text == expected_text
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import collections
|
|||
|
||||
from rhodecode.lib.partial_renderer import PyramidPartialRenderer
|
||||
from rhodecode.lib.utils2 import AttributeDict
|
||||
from rhodecode.model.db import User
|
||||
from rhodecode.model.db import User, PullRequestReviewers
|
||||
from rhodecode.model.notification import EmailNotificationModel
|
||||
|
||||
|
||||
|
|
@ -52,7 +52,8 @@ def test_render_email(app, http_host_only_stub):
|
|||
assert 'Email Body' in body
|
||||
|
||||
|
||||
def test_render_pr_email(app, user_admin):
|
||||
@pytest.mark.parametrize('role', PullRequestReviewers.ROLES)
|
||||
def test_render_pr_email(app, user_admin, role):
|
||||
ref = collections.namedtuple(
|
||||
'Ref', 'name, type')('fxies123', 'book')
|
||||
|
||||
|
|
@ -75,13 +76,17 @@ def test_render_pr_email(app, user_admin):
|
|||
'pull_request_source_repo_url': 'x',
|
||||
|
||||
'pull_request_url': 'http://localhost/pr1',
|
||||
'user_role': role,
|
||||
}
|
||||
|
||||
subject, body, body_plaintext = EmailNotificationModel().render_email(
|
||||
EmailNotificationModel.TYPE_PULL_REQUEST, **kwargs)
|
||||
|
||||
# subject
|
||||
assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
|
||||
if role == PullRequestReviewers.ROLE_REVIEWER:
|
||||
assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
|
||||
elif role == PullRequestReviewers.ROLE_OBSERVER:
|
||||
assert subject == '@test_admin (RhodeCode Admin) added you as observer to pull request. !200: "Example Pull Request"'
|
||||
|
||||
|
||||
def test_render_pr_update_email(app, user_admin):
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ class TestPullRequestModel(object):
|
|||
|
||||
def test_get_awaiting_my_review(self, pull_request):
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request, [(pull_request.author, ['author'], False, [])],
|
||||
pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
|
||||
pull_request.author)
|
||||
Session().commit()
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ class TestPullRequestModel(object):
|
|||
|
||||
def test_count_awaiting_my_review(self, pull_request):
|
||||
PullRequestModel().update_reviewers(
|
||||
pull_request, [(pull_request.author, ['author'], False, [])],
|
||||
pull_request, [(pull_request.author, ['author'], False, 'reviewer', [])],
|
||||
pull_request.author)
|
||||
Session().commit()
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ from rhodecode.lib.utils2 import AttributeDict
|
|||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.comment import CommentsModel
|
||||
from rhodecode.model.db import (
|
||||
PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
|
||||
UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
|
||||
PullRequest, PullRequestReviewers, Repository, RhodeCodeSetting, ChangesetStatus,
|
||||
RepoGroup, UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.pull_request import PullRequestModel
|
||||
from rhodecode.model.repo import RepoModel
|
||||
|
|
@ -968,7 +968,7 @@ class PRTestUtility(object):
|
|||
def create_pull_request(
|
||||
self, commits=None, target_head=None, source_head=None,
|
||||
revisions=None, approved=False, author=None, mergeable=False,
|
||||
enable_notifications=True, name_suffix=u'', reviewers=None,
|
||||
enable_notifications=True, name_suffix=u'', reviewers=None, observers=None,
|
||||
title=u"Test", description=u"Description"):
|
||||
self.set_mergeable(mergeable)
|
||||
if not enable_notifications:
|
||||
|
|
@ -1005,6 +1005,7 @@ class PRTestUtility(object):
|
|||
'target_ref': self._default_branch_reference(target_head),
|
||||
'revisions': [self.commit_ids[r] for r in revisions],
|
||||
'reviewers': reviewers or self._get_reviewers(),
|
||||
'observers': observers or self._get_observers(),
|
||||
'title': title,
|
||||
'description': description,
|
||||
}
|
||||
|
|
@ -1037,9 +1038,15 @@ class PRTestUtility(object):
|
|||
return reference
|
||||
|
||||
def _get_reviewers(self):
|
||||
role = PullRequestReviewers.ROLE_REVIEWER
|
||||
return [
|
||||
(TEST_USER_REGULAR_LOGIN, ['default1'], False, []),
|
||||
(TEST_USER_REGULAR2_LOGIN, ['default2'], False, []),
|
||||
(TEST_USER_REGULAR_LOGIN, ['default1'], False, role, []),
|
||||
(TEST_USER_REGULAR2_LOGIN, ['default2'], False, role, []),
|
||||
]
|
||||
|
||||
def _get_observers(self):
|
||||
return [
|
||||
|
||||
]
|
||||
|
||||
def update_source_repository(self, head=None):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue