##// END OF EJS Templates
pull-requests: added update pull-requests email+notifications...
marcink -
r4120:7cd93c2b default
parent child Browse files
Show More
@@ -0,0 +1,164 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
5 ## EMAIL SUBJECT
6 <%def name="subject()" filter="n,trim,whitespace_filter">
7 <%
8 data = {
9 'updating_user': '@'+h.person(updating_user),
10 'pr_id': pull_request.pull_request_id,
11 'pr_title': pull_request.title,
12 }
13 %>
14
15 ${_('{updating_user} updated pull request. !{pr_id}: "{pr_title}"').format(**data) |n}
16 </%def>
17
18 ## PLAINTEXT VERSION OF BODY
19 <%def name="body_plaintext()" filter="n,trim">
20 <%
21 data = {
22 'updating_user': h.person(updating_user),
23 'pr_id': pull_request.pull_request_id,
24 'pr_title': pull_request.title,
25 'source_ref_type': pull_request.source_ref_parts.type,
26 'source_ref_name': pull_request.source_ref_parts.name,
27 'target_ref_type': pull_request.target_ref_parts.type,
28 'target_ref_name': pull_request.target_ref_parts.name,
29 'repo_url': pull_request_source_repo_url,
30 'source_repo': pull_request_source_repo.repo_name,
31 'target_repo': pull_request_target_repo.repo_name,
32 'source_repo_url': pull_request_source_repo_url,
33 'target_repo_url': pull_request_target_repo_url,
34 }
35 %>
36
37 * ${_('Pull Request link')}: ${pull_request_url}
38
39 * ${h.literal(_('Commit flow: {source_ref_type}:{source_ref_name} of {source_repo_url} into {target_ref_type}:{target_ref_name} of {target_repo_url}').format(**data))}
40
41 * ${_('Title')}: ${pull_request.title}
42
43 * ${_('Description')}:
44
45 ${pull_request.description | trim}
46
47 * Changed commits:
48
49 - Added: ${len(added_commits)}
50 - Removed: ${len(removed_commits)}
51
52 * Changed files:
53
54 %if not changed_files:
55 No file changes found
56 %else:
57 %for file_name in added_files:
58 - A `${file_name}`
59 %endfor
60 %for file_name in modified_files:
61 - M `${file_name}`
62 %endfor
63 %for file_name in removed_files:
64 - R `${file_name}`
65 %endfor
66 %endif
67
68 ---
69 ${self.plaintext_footer()}
70 </%def>
71 <%
72 data = {
73 'updating_user': h.person(updating_user),
74 'pr_id': pull_request.pull_request_id,
75 'pr_title': pull_request.title,
76 'source_ref_type': pull_request.source_ref_parts.type,
77 'source_ref_name': pull_request.source_ref_parts.name,
78 'target_ref_type': pull_request.target_ref_parts.type,
79 'target_ref_name': pull_request.target_ref_parts.name,
80 'repo_url': pull_request_source_repo_url,
81 'source_repo': pull_request_source_repo.repo_name,
82 'target_repo': pull_request_target_repo.repo_name,
83 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
84 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url),
85 }
86 %>
87
88 <table style="text-align:left;vertical-align:middle;width: 100%">
89 <tr>
90 <td style="width:100%;border-bottom:1px solid #dbd9da;">
91
92 <h4 style="margin: 0">
93 <div style="margin-bottom: 4px">
94 <span style="color:#7E7F7F">@${h.person(updating_user.username)}</span>
95 ${_('updated')}
96 <a href="${pull_request_url}" style="${base.link_css()}">
97 ${_('pull request.').format(**data) }
98 </a>
99 </div>
100 <div style="margin-top: 10px"></div>
101 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
102 </h4>
103
104 </td>
105 </tr>
106
107 </table>
108
109 <table style="text-align:left;vertical-align:middle;width: 100%">
110 ## spacing def
111 <tr>
112 <td style="width: 130px"></td>
113 <td></td>
114 </tr>
115
116 <tr>
117 <td style="padding-right:20px;">${_('Pull request')}:</td>
118 <td>
119 <a href="${pull_request_url}" style="${base.link_css()}">
120 !${pull_request.pull_request_id}
121 </a>
122 </td>
123 </tr>
124
125 <tr>
126 <td style="padding-right:20px;line-height:20px;">${_('Commit Flow')}:</td>
127 <td style="line-height:20px;">
128 <code>${'{}:{}'.format(data['source_ref_type'], pull_request.source_ref_parts.name)}</code> ${_('of')} ${data['source_repo_url']}
129 &rarr;
130 <code>${'{}:{}'.format(data['target_ref_type'], pull_request.target_ref_parts.name)}</code> ${_('of')} ${data['target_repo_url']}
131 </td>
132 </tr>
133
134 <tr>
135 <td style="padding-right:20px;">${_('Description')}:</td>
136 <td style="white-space:pre-wrap"><code>${pull_request.description | trim}</code></td>
137 </tr>
138 <tr>
139 <td style="padding-right:20px;">${_('Changes')}:</td>
140 <td style="white-space:pre-line">\
141 <strong>Changed commits:</strong>
142
143 - Added: ${len(added_commits)}
144 - Removed: ${len(removed_commits)}
145
146 <strong>Changed files:</strong>
147
148 %if not changed_files:
149 No file changes found
150 %else:
151 %for file_name in added_files:
152 - A <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a>
153 %endfor
154 %for file_name in modified_files:
155 - M <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a>
156 %endfor
157 %for file_name in removed_files:
158 - R <a href="${pull_request_url + '#a_' + h.FID(ancestor_commit_id, file_name)}">${file_name}</a>
159 %endfor
160 %endif
161 </td>
162 </tr>
163
164 </table>
@@ -895,7 +895,9 b' def update_pull_request('
895
895
896 with pull_request.set_state(PullRequest.STATE_UPDATING):
896 with pull_request.set_state(PullRequest.STATE_UPDATING):
897 if PullRequestModel().has_valid_update_type(pull_request):
897 if PullRequestModel().has_valid_update_type(pull_request):
898 update_response = PullRequestModel().update_commits(pull_request)
898 db_user = apiuser.get_instance()
899 update_response = PullRequestModel().update_commits(
900 pull_request, db_user)
899 commit_changes = update_response.changes or commit_changes
901 commit_changes = update_response.changes or commit_changes
900 Session().commit()
902 Session().commit()
901
903
@@ -573,8 +573,7 b' class AdminSettingsView(BaseAppView):'
573
573
574 email_kwargs = {
574 email_kwargs = {
575 'date': datetime.datetime.now(),
575 'date': datetime.datetime.now(),
576 'user': c.rhodecode_user,
576 'user': c.rhodecode_user
577 'rhodecode_version': c.rhodecode_version
578 }
577 }
579
578
580 (subject, headers, email_body,
579 (subject, headers, email_body,
@@ -78,7 +78,16 b' Check if we should use full-topic or min'
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
80 user = User.get_by_username(self.request.GET.get('user')) or self._rhodecode_db_user
81
81 # file/commit changes for PR update
82 commit_changes = AttributeDict({
83 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
84 'removed': ['eeeeeeeeeee'],
85 })
86 file_changes = AttributeDict({
87 'added': ['a/file1.md', 'file2.py'],
88 'modified': ['b/modified_file.rst'],
89 'removed': ['.idea'],
90 })
82 email_kwargs = {
91 email_kwargs = {
83 'test': {},
92 'test': {},
84 'message': {
93 'message': {
@@ -87,7 +96,6 b' Check if we should use full-topic or min'
87 'email_test': {
96 'email_test': {
88 'user': user,
97 'user': user,
89 'date': datetime.datetime.now(),
98 'date': datetime.datetime.now(),
90 'rhodecode_version': c.rhodecode_version
91 },
99 },
92 'password_reset': {
100 'password_reset': {
93 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
101 'password_reset_url': 'http://example.com/reset-rhodecode-password/token',
@@ -215,6 +223,35 b' This should work better !'
215
223
216 },
224 },
217
225
226 'pull_request_update': {
227 'updating_user': user,
228
229 'status_change': None,
230 'status_change_type': None,
231
232 'pull_request': pr,
233 'pull_request_commits': [],
234
235 'pull_request_target_repo': target_repo,
236 'pull_request_target_repo_url': 'http://target-repo/url',
237
238 'pull_request_source_repo': source_repo,
239 'pull_request_source_repo_url': 'http://source-repo/url',
240
241 'pull_request_url': 'http://localhost/pr1',
242
243 # update comment links
244 'pr_comment_url': 'http://comment-url',
245 'pr_comment_reply_url': 'http://comment-url#reply',
246 'ancestor_commit_id': 'f39bd443',
247 'added_commits': commit_changes.added,
248 'removed_commits': commit_changes.removed,
249 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
250 'added_files': file_changes.added,
251 'modified_files': file_changes.modified,
252 'removed_files': file_changes.removed,
253 },
254
218 'cs_comment': {
255 'cs_comment': {
219 'user': user,
256 'user': user,
220 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
257 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
@@ -338,6 +375,8 b' users: description edit fixes'
338
375
339 'pull_request_comment+file': {},
376 'pull_request_comment+file': {},
340 'pull_request_comment+status': {},
377 'pull_request_comment+status': {},
378
379 'pull_request_update': {},
341 }
380 }
342 c.email_types.update(EmailNotificationModel.email_types)
381 c.email_types.update(EmailNotificationModel.email_types)
343
382
@@ -1117,7 +1117,8 b' class RepoPullRequestsView(RepoAppView, '
1117 _ = self.request.translate
1117 _ = self.request.translate
1118
1118
1119 with pull_request.set_state(PullRequest.STATE_UPDATING):
1119 with pull_request.set_state(PullRequest.STATE_UPDATING):
1120 resp = PullRequestModel().update_commits(pull_request)
1120 resp = PullRequestModel().update_commits(
1121 pull_request, self._rhodecode_db_user)
1121
1122
1122 if resp.executed:
1123 if resp.executed:
1123
1124
@@ -821,7 +821,7 b' def is_svn_without_proxy(repository):'
821
821
822 def discover_user(author):
822 def discover_user(author):
823 """
823 """
824 Tries to discover RhodeCode User based on the autho string. Author string
824 Tries to discover RhodeCode User based on the author string. Author string
825 is typically `FirstName LastName <email@address.com>`
825 is typically `FirstName LastName <email@address.com>`
826 """
826 """
827
827
@@ -1015,10 +1015,11 b' def bool2icon(value, show_at_false=True)'
1015 #==============================================================================
1015 #==============================================================================
1016 # PERMS
1016 # PERMS
1017 #==============================================================================
1017 #==============================================================================
1018 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1018 from rhodecode.lib.auth import (
1019 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1019 HasPermissionAny, HasPermissionAll,
1020 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1020 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1021 csrf_token_key
1021 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1022 csrf_token_key, AuthUser)
1022
1023
1023
1024
1024 #==============================================================================
1025 #==============================================================================
@@ -4401,6 +4401,7 b' class Notification(Base, BaseModel):'
4401 TYPE_REGISTRATION = u'registration'
4401 TYPE_REGISTRATION = u'registration'
4402 TYPE_PULL_REQUEST = u'pull_request'
4402 TYPE_PULL_REQUEST = u'pull_request'
4403 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4403 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4404 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4404
4405
4405 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4406 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4406 subject = Column('subject', Unicode(512), nullable=True)
4407 subject = Column('subject', Unicode(512), nullable=True)
@@ -293,6 +293,7 b' class EmailNotificationModel(BaseModel):'
293 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
293 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
294 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
294 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
295 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
295 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
296 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
296 TYPE_MAIN = Notification.TYPE_MESSAGE
297 TYPE_MAIN = Notification.TYPE_MESSAGE
297
298
298 TYPE_PASSWORD_RESET = 'password_reset'
299 TYPE_PASSWORD_RESET = 'password_reset'
@@ -319,6 +320,8 b' class EmailNotificationModel(BaseModel):'
319 'rhodecode:templates/email_templates/pull_request_review.mako',
320 'rhodecode:templates/email_templates/pull_request_review.mako',
320 TYPE_PULL_REQUEST_COMMENT:
321 TYPE_PULL_REQUEST_COMMENT:
321 'rhodecode:templates/email_templates/pull_request_comment.mako',
322 'rhodecode:templates/email_templates/pull_request_comment.mako',
323 TYPE_PULL_REQUEST_UPDATE:
324 'rhodecode:templates/email_templates/pull_request_update.mako',
322 }
325 }
323
326
324 def __init__(self):
327 def __init__(self):
@@ -341,6 +344,7 b' class EmailNotificationModel(BaseModel):'
341 """
344 """
342
345
343 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
346 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
347 kwargs['rhodecode_version'] = rhodecode.__version__
344 instance_url = h.route_url('home')
348 instance_url = h.route_url('home')
345 _kwargs = {
349 _kwargs = {
346 'instance_url': instance_url,
350 'instance_url': instance_url,
@@ -65,9 +65,19 b' log = logging.getLogger(__name__)'
65
65
66 # Data structure to hold the response data when updating commits during a pull
66 # Data structure to hold the response data when updating commits during a pull
67 # request update.
67 # request update.
68 UpdateResponse = collections.namedtuple('UpdateResponse', [
68 class UpdateResponse(object):
69 'executed', 'reason', 'new', 'old', 'changes',
69
70 'source_changed', 'target_changed'])
70 def __init__(self, executed, reason, new, old, common_ancestor_id,
71 commit_changes, source_changed, target_changed):
72
73 self.executed = executed
74 self.reason = reason
75 self.new = new
76 self.old = old
77 self.common_ancestor_id = common_ancestor_id
78 self.changes = commit_changes
79 self.source_changed = source_changed
80 self.target_changed = target_changed
71
81
72
82
73 class PullRequestModel(BaseModel):
83 class PullRequestModel(BaseModel):
@@ -672,11 +682,13 b' class PullRequestModel(BaseModel):'
672 source_ref_type = pull_request.source_ref_parts.type
682 source_ref_type = pull_request.source_ref_parts.type
673 return source_ref_type in self.REF_TYPES
683 return source_ref_type in self.REF_TYPES
674
684
675 def update_commits(self, pull_request):
685 def update_commits(self, pull_request, updating_user):
676 """
686 """
677 Get the updated list of commits for the pull request
687 Get the updated list of commits for the pull request
678 and return the new pull request version and the list
688 and return the new pull request version and the list
679 of commits processed by this update action
689 of commits processed by this update action
690
691 updating_user is the user_object who triggered the update
680 """
692 """
681 pull_request = self.__get_pull_request(pull_request)
693 pull_request = self.__get_pull_request(pull_request)
682 source_ref_type = pull_request.source_ref_parts.type
694 source_ref_type = pull_request.source_ref_parts.type
@@ -693,7 +705,7 b' class PullRequestModel(BaseModel):'
693 return UpdateResponse(
705 return UpdateResponse(
694 executed=False,
706 executed=False,
695 reason=UpdateFailureReason.WRONG_REF_TYPE,
707 reason=UpdateFailureReason.WRONG_REF_TYPE,
696 old=pull_request, new=None, changes=None,
708 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
697 source_changed=False, target_changed=False)
709 source_changed=False, target_changed=False)
698
710
699 # source repo
711 # source repo
@@ -705,7 +717,7 b' class PullRequestModel(BaseModel):'
705 return UpdateResponse(
717 return UpdateResponse(
706 executed=False,
718 executed=False,
707 reason=UpdateFailureReason.MISSING_SOURCE_REF,
719 reason=UpdateFailureReason.MISSING_SOURCE_REF,
708 old=pull_request, new=None, changes=None,
720 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
709 source_changed=False, target_changed=False)
721 source_changed=False, target_changed=False)
710
722
711 source_changed = source_ref_id != source_commit.raw_id
723 source_changed = source_ref_id != source_commit.raw_id
@@ -719,7 +731,7 b' class PullRequestModel(BaseModel):'
719 return UpdateResponse(
731 return UpdateResponse(
720 executed=False,
732 executed=False,
721 reason=UpdateFailureReason.MISSING_TARGET_REF,
733 reason=UpdateFailureReason.MISSING_TARGET_REF,
722 old=pull_request, new=None, changes=None,
734 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
723 source_changed=False, target_changed=False)
735 source_changed=False, target_changed=False)
724 target_changed = target_ref_id != target_commit.raw_id
736 target_changed = target_ref_id != target_commit.raw_id
725
737
@@ -728,7 +740,7 b' class PullRequestModel(BaseModel):'
728 return UpdateResponse(
740 return UpdateResponse(
729 executed=False,
741 executed=False,
730 reason=UpdateFailureReason.NO_CHANGE,
742 reason=UpdateFailureReason.NO_CHANGE,
731 old=pull_request, new=None, changes=None,
743 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
732 source_changed=target_changed, target_changed=source_changed)
744 source_changed=target_changed, target_changed=source_changed)
733
745
734 change_in_found = 'target repo' if target_changed else 'source repo'
746 change_in_found = 'target repo' if target_changed else 'source repo'
@@ -759,7 +771,7 b' class PullRequestModel(BaseModel):'
759 return UpdateResponse(
771 return UpdateResponse(
760 executed=False,
772 executed=False,
761 reason=UpdateFailureReason.MISSING_TARGET_REF,
773 reason=UpdateFailureReason.MISSING_TARGET_REF,
762 old=pull_request, new=None, changes=None,
774 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
763 source_changed=source_changed, target_changed=target_changed)
775 source_changed=source_changed, target_changed=target_changed)
764
776
765 # re-compute commit ids
777 # re-compute commit ids
@@ -769,13 +781,13 b' class PullRequestModel(BaseModel):'
769 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
781 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
770 pre_load=pre_load)
782 pre_load=pre_load)
771
783
772 ancestor = source_repo.get_common_ancestor(
784 ancestor_commit_id = source_repo.get_common_ancestor(
773 source_commit.raw_id, target_commit.raw_id, target_repo)
785 source_commit.raw_id, target_commit.raw_id, target_repo)
774
786
775 pull_request.source_ref = '%s:%s:%s' % (
787 pull_request.source_ref = '%s:%s:%s' % (
776 source_ref_type, source_ref_name, source_commit.raw_id)
788 source_ref_type, source_ref_name, source_commit.raw_id)
777 pull_request.target_ref = '%s:%s:%s' % (
789 pull_request.target_ref = '%s:%s:%s' % (
778 target_ref_type, target_ref_name, ancestor)
790 target_ref_type, target_ref_name, ancestor_commit_id)
779
791
780 pull_request.revisions = [
792 pull_request.revisions = [
781 commit.raw_id for commit in reversed(commit_ranges)]
793 commit.raw_id for commit in reversed(commit_ranges)]
@@ -787,7 +799,7 b' class PullRequestModel(BaseModel):'
787 pull_request, pull_request_version)
799 pull_request, pull_request_version)
788
800
789 # calculate commit and file changes
801 # calculate commit and file changes
790 changes = self._calculate_commit_id_changes(
802 commit_changes = self._calculate_commit_id_changes(
791 old_commit_ids, new_commit_ids)
803 old_commit_ids, new_commit_ids)
792 file_changes = self._calculate_file_changes(
804 file_changes = self._calculate_file_changes(
793 old_diff_data, new_diff_data)
805 old_diff_data, new_diff_data)
@@ -797,23 +809,23 b' class PullRequestModel(BaseModel):'
797 pull_request, old_diff_data=old_diff_data,
809 pull_request, old_diff_data=old_diff_data,
798 new_diff_data=new_diff_data)
810 new_diff_data=new_diff_data)
799
811
800 commit_changes = (changes.added or changes.removed)
812 valid_commit_changes = (commit_changes.added or commit_changes.removed)
801 file_node_changes = (
813 file_node_changes = (
802 file_changes.added or file_changes.modified or file_changes.removed)
814 file_changes.added or file_changes.modified or file_changes.removed)
803 pr_has_changes = commit_changes or file_node_changes
815 pr_has_changes = valid_commit_changes or file_node_changes
804
816
805 # Add an automatic comment to the pull request, in case
817 # Add an automatic comment to the pull request, in case
806 # anything has changed
818 # anything has changed
807 if pr_has_changes:
819 if pr_has_changes:
808 update_comment = CommentsModel().create(
820 update_comment = CommentsModel().create(
809 text=self._render_update_message(changes, file_changes),
821 text=self._render_update_message(ancestor_commit_id, commit_changes, file_changes),
810 repo=pull_request.target_repo,
822 repo=pull_request.target_repo,
811 user=pull_request.author,
823 user=pull_request.author,
812 pull_request=pull_request,
824 pull_request=pull_request,
813 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
825 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
814
826
815 # Update status to "Under Review" for added commits
827 # Update status to "Under Review" for added commits
816 for commit_id in changes.added:
828 for commit_id in commit_changes.added:
817 ChangesetStatusModel().set_status(
829 ChangesetStatusModel().set_status(
818 repo=pull_request.source_repo,
830 repo=pull_request.source_repo,
819 status=ChangesetStatus.STATUS_UNDER_REVIEW,
831 status=ChangesetStatus.STATUS_UNDER_REVIEW,
@@ -822,10 +834,19 b' class PullRequestModel(BaseModel):'
822 pull_request=pull_request,
834 pull_request=pull_request,
823 revision=commit_id)
835 revision=commit_id)
824
836
837 # send update email to users
838 try:
839 self.notify_users(pull_request=pull_request, updating_user=updating_user,
840 ancestor_commit_id=ancestor_commit_id,
841 commit_changes=commit_changes,
842 file_changes=file_changes)
843 except Exception:
844 log.exception('Failed to send email notification to users')
845
825 log.debug(
846 log.debug(
826 'Updated pull request %s, added_ids: %s, common_ids: %s, '
847 'Updated pull request %s, added_ids: %s, common_ids: %s, '
827 'removed_ids: %s', pull_request.pull_request_id,
848 'removed_ids: %s', pull_request.pull_request_id,
828 changes.added, changes.common, changes.removed)
849 commit_changes.added, commit_changes.common, commit_changes.removed)
829 log.debug(
850 log.debug(
830 'Updated pull request with the following file changes: %s',
851 'Updated pull request with the following file changes: %s',
831 file_changes)
852 file_changes)
@@ -841,7 +862,8 b' class PullRequestModel(BaseModel):'
841
862
842 return UpdateResponse(
863 return UpdateResponse(
843 executed=True, reason=UpdateFailureReason.NONE,
864 executed=True, reason=UpdateFailureReason.NONE,
844 old=pull_request, new=pull_request_version, changes=changes,
865 old=pull_request, new=pull_request_version,
866 common_ancestor_id=ancestor_commit_id, commit_changes=commit_changes,
845 source_changed=source_changed, target_changed=target_changed)
867 source_changed=source_changed, target_changed=target_changed)
846
868
847 def _create_version_from_snapshot(self, pull_request):
869 def _create_version_from_snapshot(self, pull_request):
@@ -963,12 +985,13 b' class PullRequestModel(BaseModel):'
963
985
964 return FileChangeTuple(added_files, modified_files, removed_files)
986 return FileChangeTuple(added_files, modified_files, removed_files)
965
987
966 def _render_update_message(self, changes, file_changes):
988 def _render_update_message(self, ancestor_commit_id, changes, file_changes):
967 """
989 """
968 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
990 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
969 so it's always looking the same disregarding on which default
991 so it's always looking the same disregarding on which default
970 renderer system is using.
992 renderer system is using.
971
993
994 :param ancestor_commit_id: ancestor raw_id
972 :param changes: changes named tuple
995 :param changes: changes named tuple
973 :param file_changes: file changes named tuple
996 :param file_changes: file changes named tuple
974
997
@@ -987,6 +1010,7 b' class PullRequestModel(BaseModel):'
987 'added_files': file_changes.added,
1010 'added_files': file_changes.added,
988 'modified_files': file_changes.modified,
1011 'modified_files': file_changes.modified,
989 'removed_files': file_changes.removed,
1012 'removed_files': file_changes.removed,
1013 'ancestor_commit_id': ancestor_commit_id
990 }
1014 }
991 renderer = RstTemplateRenderer()
1015 renderer = RstTemplateRenderer()
992 return renderer.render('pull_request_update.mako', **params)
1016 return renderer.render('pull_request_update.mako', **params)
@@ -1170,6 +1194,75 b' class PullRequestModel(BaseModel):'
1170 email_kwargs=kwargs,
1194 email_kwargs=kwargs,
1171 )
1195 )
1172
1196
1197 def notify_users(self, pull_request, updating_user, ancestor_commit_id,
1198 commit_changes, file_changes):
1199
1200 updating_user_id = updating_user.user_id
1201 reviewers = set([x.user.user_id for x in pull_request.reviewers])
1202 # NOTE(marcink): send notification to all other users except to
1203 # person who updated the PR
1204 recipients = reviewers.difference(set([updating_user_id]))
1205
1206 log.debug('Notify following recipients about pull-request update %s', recipients)
1207
1208 pull_request_obj = pull_request
1209
1210 # send email about the update
1211 changed_files = (
1212 file_changes.added + file_changes.modified + file_changes.removed)
1213
1214 pr_source_repo = pull_request_obj.source_repo
1215 pr_target_repo = pull_request_obj.target_repo
1216
1217 pr_url = h.route_url('pullrequest_show',
1218 repo_name=pr_target_repo.repo_name,
1219 pull_request_id=pull_request_obj.pull_request_id,)
1220
1221 # set some variables for email notification
1222 pr_target_repo_url = h.route_url(
1223 'repo_summary', repo_name=pr_target_repo.repo_name)
1224
1225 pr_source_repo_url = h.route_url(
1226 'repo_summary', repo_name=pr_source_repo.repo_name)
1227
1228 email_kwargs = {
1229 'date': datetime.datetime.now(),
1230 'updating_user': updating_user,
1231
1232 'pull_request': pull_request_obj,
1233
1234 'pull_request_target_repo': pr_target_repo,
1235 'pull_request_target_repo_url': pr_target_repo_url,
1236
1237 'pull_request_source_repo': pr_source_repo,
1238 'pull_request_source_repo_url': pr_source_repo_url,
1239
1240 'pull_request_url': pr_url,
1241
1242 'ancestor_commit_id': ancestor_commit_id,
1243 'added_commits': commit_changes.added,
1244 'removed_commits': commit_changes.removed,
1245 'changed_files': changed_files,
1246 'added_files': file_changes.added,
1247 'modified_files': file_changes.modified,
1248 'removed_files': file_changes.removed,
1249 }
1250
1251 (subject,
1252 _h, _e, # we don't care about those
1253 body_plaintext) = EmailNotificationModel().render_email(
1254 EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **email_kwargs)
1255
1256 # create notification objects, and emails
1257 NotificationModel().create(
1258 created_by=updating_user,
1259 notification_subject=subject,
1260 notification_body=body_plaintext,
1261 notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE,
1262 recipients=recipients,
1263 email_kwargs=email_kwargs,
1264 )
1265
1173 def delete(self, pull_request, user):
1266 def delete(self, pull_request, user):
1174 pull_request = self.__get_pull_request(pull_request)
1267 pull_request = self.__get_pull_request(pull_request)
1175 old_data = pull_request.get_api_data(with_merge_state=False)
1268 old_data = pull_request.get_api_data(with_merge_state=False)
@@ -115,17 +115,17 b' data = {'
115 <td style="width:100%;border-bottom:1px solid #dbd9da;">
115 <td style="width:100%;border-bottom:1px solid #dbd9da;">
116
116
117 <h4 style="margin: 0">
117 <h4 style="margin: 0">
118 <div style="margin-bottom: 4px; color:#7E7F7F">
118 <div style="margin-bottom: 4px">
119 @${h.person(user.username)}
119 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
120 ${_('left a')}
121 <a href="${pr_comment_url}" style="${base.link_css()}">
122 % if comment_file:
123 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
124 % else:
125 ${_('{comment_type} on pull request.').format(**data) |n}
126 % endif
127 </a>
120 </div>
128 </div>
121 ${_('left a')}
122 <a href="${pr_comment_url}" style="${base.link_css()}">
123 % if comment_file:
124 ${_('{comment_type} on file `{comment_file}` in pull request.').format(**data)}
125 % else:
126 ${_('{comment_type} on pull request.').format(**data) |n}
127 % endif
128 </a>
129 <div style="margin-top: 10px"></div>
129 <div style="margin-top: 10px"></div>
130 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
130 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
131 </h4>
131 </h4>
@@ -78,13 +78,13 b' data = {'
78 <td style="width:100%;border-bottom:1px solid #dbd9da;">
78 <td style="width:100%;border-bottom:1px solid #dbd9da;">
79
79
80 <h4 style="margin: 0">
80 <h4 style="margin: 0">
81 <div style="margin-bottom: 4px; color:#7E7F7F">
81 <div style="margin-bottom: 4px">
82 @${h.person(user.username)}
82 <span style="color:#7E7F7F">@${h.person(user.username)}</span>
83 ${_('requested a')}
84 <a href="${pull_request_url}" style="${base.link_css()}">
85 ${_('pull request review.').format(**data) }
86 </a>
83 </div>
87 </div>
84 ${_('requested a')}
85 <a href="${pull_request_url}" style="${base.link_css()}">
86 ${_('pull request review.').format(**data) }
87 </a>
88 <div style="margin-top: 10px"></div>
88 <div style="margin-top: 10px"></div>
89 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
89 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
90 </h4>
90 </h4>
@@ -14,13 +14,13 b' Pull request updated. Auto status change'
14 %else:
14 %else:
15 Changed files:
15 Changed files:
16 %for file_name in added_files:
16 %for file_name in added_files:
17 * `A ${file_name} <#${'a_' + h.FID('', file_name)}>`_
17 * `A ${file_name} <#${'a_' + h.FID(ancestor_commit_id, file_name)}>`_
18 %endfor
18 %endfor
19 %for file_name in modified_files:
19 %for file_name in modified_files:
20 * `M ${file_name} <#${'a_' + h.FID('', file_name)}>`_
20 * `M ${file_name} <#${'a_' + h.FID(ancestor_commit_id, file_name)}>`_
21 %endfor
21 %endfor
22 %for file_name in removed_files:
22 %for file_name in removed_files:
23 * R ${file_name}
23 * `R ${file_name}`
24 %endfor
24 %endfor
25 %endif
25 %endif
26
26
@@ -87,6 +87,59 b' def test_render_pr_email(app, user_admin'
87 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
87 assert subject == '@test_admin (RhodeCode Admin) requested a pull request review. !200: "Example Pull Request"'
88
88
89
89
90 def test_render_pr_update_email(app, user_admin):
91 ref = collections.namedtuple(
92 'Ref', 'name, type')('fxies123', 'book')
93
94 pr = collections.namedtuple('PullRequest',
95 'pull_request_id, title, description, source_ref_parts, source_ref_name, target_ref_parts, target_ref_name')(
96 200, 'Example Pull Request', 'Desc of PR', ref, 'bookmark', ref, 'Branch')
97
98 source_repo = target_repo = collections.namedtuple(
99 'Repo', 'type, repo_name')('hg', 'pull_request_1')
100
101 commit_changes = AttributeDict({
102 'added': ['aaaaaaabbbbb', 'cccccccddddddd'],
103 'removed': ['eeeeeeeeeee'],
104 })
105 file_changes = AttributeDict({
106 'added': ['a/file1.md', 'file2.py'],
107 'modified': ['b/modified_file.rst'],
108 'removed': ['.idea'],
109 })
110
111 kwargs = {
112 'updating_user': User.get_first_super_admin(),
113
114 'pull_request': pr,
115 'pull_request_commits': [],
116
117 'pull_request_target_repo': target_repo,
118 'pull_request_target_repo_url': 'x',
119
120 'pull_request_source_repo': source_repo,
121 'pull_request_source_repo_url': 'x',
122
123 'pull_request_url': 'http://localhost/pr1',
124
125 'pr_comment_url': 'http://comment-url',
126 'pr_comment_reply_url': 'http://comment-url#reply',
127 'ancestor_commit_id': 'f39bd443',
128 'added_commits': commit_changes.added,
129 'removed_commits': commit_changes.removed,
130 'changed_files': (file_changes.added + file_changes.modified + file_changes.removed),
131 'added_files': file_changes.added,
132 'modified_files': file_changes.modified,
133 'removed_files': file_changes.removed,
134 }
135
136 subject, headers, body, body_plaintext = EmailNotificationModel().render_email(
137 EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE, **kwargs)
138
139 # subject
140 assert subject == '@test_admin (RhodeCode Admin) updated pull request. !200: "Example Pull Request"'
141
142
90 @pytest.mark.parametrize('mention', [
143 @pytest.mark.parametrize('mention', [
91 True,
144 True,
92 False
145 False
@@ -538,6 +538,7 b' Pull request updated. Auto status change'
538 'added_files': [],
538 'added_files': [],
539 'modified_files': [],
539 'modified_files': [],
540 'removed_files': [],
540 'removed_files': [],
541 'ancestor_commit_id': 'aaabbbcccdddeee',
541 }
542 }
542 renderer = RstTemplateRenderer()
543 renderer = RstTemplateRenderer()
543 rendered = renderer.render('pull_request_update.mako', **params)
544 rendered = renderer.render('pull_request_update.mako', **params)
@@ -557,11 +558,11 b' Pull request updated. Auto status change'
557 * :removed:`3 removed`
558 * :removed:`3 removed`
558
559
559 Changed files:
560 Changed files:
560 * `A /path/a.py <#a_c--68ed34923b68>`_
561 * `A /path/a.py <#a_c-aaabbbcccddd-68ed34923b68>`_
561 * `A /path/b.js <#a_c--64f90608b607>`_
562 * `A /path/b.js <#a_c-aaabbbcccddd-64f90608b607>`_
562 * `M /path/d.js <#a_c--85842bf30c6e>`_
563 * `M /path/d.js <#a_c-aaabbbcccddd-85842bf30c6e>`_
563 * `M /path/ę.py <#a_c--d713adf009cd>`_
564 * `M /path/ę.py <#a_c-aaabbbcccddd-d713adf009cd>`_
564 * R /path/ź.py
565 * `R /path/ź.py`
565
566
566 .. |under_review| replace:: *"NEW STATUS"*'''
567 .. |under_review| replace:: *"NEW STATUS"*'''
567
568
@@ -577,6 +578,7 b' Pull request updated. Auto status change'
577 'added_files': added,
578 'added_files': added,
578 'modified_files': modified,
579 'modified_files': modified,
579 'removed_files': removed,
580 'removed_files': removed,
581 'ancestor_commit_id': 'aaabbbcccdddeee',
580 }
582 }
581 renderer = RstTemplateRenderer()
583 renderer = RstTemplateRenderer()
582 rendered = renderer.render('pull_request_update.mako', **params)
584 rendered = renderer.render('pull_request_update.mako', **params)
@@ -81,7 +81,7 b' def test_pull_request_stays_if_update_wi'
81 voted_status, *pull_request.reviewers)
81 voted_status, *pull_request.reviewers)
82
82
83 # Update, without change
83 # Update, without change
84 PullRequestModel().update_commits(pull_request)
84 PullRequestModel().update_commits(pull_request, pull_request.author)
85
85
86 # Expect that review status is the voted_status
86 # Expect that review status is the voted_status
87 expected_review_status = voted_status
87 expected_review_status = voted_status
@@ -100,7 +100,7 b' def test_pull_request_under_review_if_up'
100
100
101 # Update, with change
101 # Update, with change
102 pr_util.update_source_repository()
102 pr_util.update_source_repository()
103 PullRequestModel().update_commits(pull_request)
103 PullRequestModel().update_commits(pull_request, pull_request.author)
104
104
105 # Expect that review status is the voted_status
105 # Expect that review status is the voted_status
106 expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW
106 expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW
@@ -171,7 +171,7 b' def test_commit_keeps_status_if_unchange'
171 commit_id = pull_request.revisions[-1]
171 commit_id = pull_request.revisions[-1]
172 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
172 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
173 pr_util.update_source_repository()
173 pr_util.update_source_repository()
174 PullRequestModel().update_commits(pull_request)
174 PullRequestModel().update_commits(pull_request, pull_request.author)
175 assert pull_request.revisions[-1] == commit_id
175 assert pull_request.revisions[-1] == commit_id
176
176
177 status = ChangesetStatusModel().get_status(
177 status = ChangesetStatusModel().get_status(
@@ -814,7 +814,7 b' def test_update_writes_snapshot_into_pul'
814 pull_request = pr_util.create_pull_request()
814 pull_request = pr_util.create_pull_request()
815 pr_util.update_source_repository()
815 pr_util.update_source_repository()
816
816
817 model.update_commits(pull_request)
817 model.update_commits(pull_request, pull_request.author)
818
818
819 # Expect that it has a version entry now
819 # Expect that it has a version entry now
820 assert len(model.get_versions(pull_request)) == 1
820 assert len(model.get_versions(pull_request)) == 1
@@ -823,7 +823,7 b' def test_update_writes_snapshot_into_pul'
823 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
823 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
824 pull_request = pr_util.create_pull_request()
824 pull_request = pr_util.create_pull_request()
825 model = PullRequestModel()
825 model = PullRequestModel()
826 model.update_commits(pull_request)
826 model.update_commits(pull_request, pull_request.author)
827
827
828 # Expect that it still has no versions
828 # Expect that it still has no versions
829 assert len(model.get_versions(pull_request)) == 0
829 assert len(model.get_versions(pull_request)) == 0
@@ -835,7 +835,7 b' def test_update_assigns_comments_to_the_'
835 comment = pr_util.create_comment()
835 comment = pr_util.create_comment()
836 pr_util.update_source_repository()
836 pr_util.update_source_repository()
837
837
838 model.update_commits(pull_request)
838 model.update_commits(pull_request, pull_request.author)
839
839
840 # Expect that the comment is linked to the pr version now
840 # Expect that the comment is linked to the pr version now
841 assert comment.pull_request_version == model.get_versions(pull_request)[0]
841 assert comment.pull_request_version == model.get_versions(pull_request)[0]
@@ -847,8 +847,9 b' def test_update_adds_a_comment_to_the_pu'
847 pr_util.update_source_repository()
847 pr_util.update_source_repository()
848 pr_util.update_source_repository()
848 pr_util.update_source_repository()
849
849
850 model.update_commits(pull_request)
850 update_response = model.update_commits(pull_request, pull_request.author)
851
851
852 commit_id = update_response.common_ancestor_id
852 # Expect to find a new comment about the change
853 # Expect to find a new comment about the change
853 expected_message = textwrap.dedent(
854 expected_message = textwrap.dedent(
854 """\
855 """\
@@ -863,10 +864,10 b' def test_update_adds_a_comment_to_the_pu'
863 * :removed:`0 removed`
864 * :removed:`0 removed`
864
865
865 Changed files:
866 Changed files:
866 * `A file_2 <#a_c--92ed3b5f07b4>`_
867 * `A file_2 <#a_c-{}-92ed3b5f07b4>`_
867
868
868 .. |under_review| replace:: *"Under Review"*"""
869 .. |under_review| replace:: *"Under Review"*"""
869 )
870 ).format(commit_id[:12])
870 pull_request_comments = sorted(
871 pull_request_comments = sorted(
871 pull_request.comments, key=lambda c: c.modified_at)
872 pull_request.comments, key=lambda c: c.modified_at)
872 update_comment = pull_request_comments[-1]
873 update_comment = pull_request_comments[-1]
@@ -1047,7 +1047,7 b' class PRTestUtility(object):'
1047 def add_one_commit(self, head=None):
1047 def add_one_commit(self, head=None):
1048 self.update_source_repository(head=head)
1048 self.update_source_repository(head=head)
1049 old_commit_ids = set(self.pull_request.revisions)
1049 old_commit_ids = set(self.pull_request.revisions)
1050 PullRequestModel().update_commits(self.pull_request)
1050 PullRequestModel().update_commits(self.pull_request, self.pull_request.author)
1051 commit_ids = set(self.pull_request.revisions)
1051 commit_ids = set(self.pull_request.revisions)
1052 new_commit_ids = commit_ids - old_commit_ids
1052 new_commit_ids = commit_ids - old_commit_ids
1053 assert len(new_commit_ids) == 1
1053 assert len(new_commit_ids) == 1
@@ -1066,7 +1066,7 b' class PRTestUtility(object):'
1066 kwargs = {}
1066 kwargs = {}
1067 source_vcs.strip(removed_commit_id, **kwargs)
1067 source_vcs.strip(removed_commit_id, **kwargs)
1068
1068
1069 PullRequestModel().update_commits(self.pull_request)
1069 PullRequestModel().update_commits(self.pull_request, self.pull_request.author)
1070 assert len(self.pull_request.revisions) == 1
1070 assert len(self.pull_request.revisions) == 1
1071 return removed_commit_id
1071 return removed_commit_id
1072
1072
General Comments 0
You need to be logged in to leave comments. Login now