reviewers: added observers as another way to define reviewers.

- fixes #4183
This commit is contained in:
Marcin Kuzminski 2020-10-06 08:49:13 +02:00
parent 274d09cad8
commit 80da773011
27 changed files with 1188 additions and 352 deletions

View file

@ -575,7 +575,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 +606,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 +620,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 +654,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(
@ -1204,23 +1232,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 +1271,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 +1306,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.get_instance())
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.get_instance())
return ids_to_add, ids_to_remove
def get_url(self, pull_request, request=None, permalink=False):
@ -1301,16 +1417,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 +1448,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 +1463,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 +1472,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,6 +1480,14 @@ 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):
@ -1874,11 +2001,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):