##// 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 896 with pull_request.set_state(PullRequest.STATE_UPDATING):
897 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 901 commit_changes = update_response.changes or commit_changes
900 902 Session().commit()
901 903
@@ -573,8 +573,7 b' class AdminSettingsView(BaseAppView):'
573 573
574 574 email_kwargs = {
575 575 'date': datetime.datetime.now(),
576 'user': c.rhodecode_user,
577 'rhodecode_version': c.rhodecode_version
576 'user': c.rhodecode_user
578 577 }
579 578
580 579 (subject, headers, email_body,
@@ -78,7 +78,16 b' Check if we should use full-topic or min'
78 78 target_repo = AttributeDict(repo_name='repo_group/target_repo')
79 79 source_repo = AttributeDict(repo_name='repo_group/source_repo')
80 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 91 email_kwargs = {
83 92 'test': {},
84 93 'message': {
@@ -87,7 +96,6 b' Check if we should use full-topic or min'
87 96 'email_test': {
88 97 'user': user,
89 98 'date': datetime.datetime.now(),
90 'rhodecode_version': c.rhodecode_version
91 99 },
92 100 'password_reset': {
93 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 255 'cs_comment': {
219 256 'user': user,
220 257 'commit': AttributeDict(idx=123, raw_id='a'*40, message='Commit message'),
@@ -338,6 +375,8 b' users: description edit fixes'
338 375
339 376 'pull_request_comment+file': {},
340 377 'pull_request_comment+status': {},
378
379 'pull_request_update': {},
341 380 }
342 381 c.email_types.update(EmailNotificationModel.email_types)
343 382
@@ -1117,7 +1117,8 b' class RepoPullRequestsView(RepoAppView, '
1117 1117 _ = self.request.translate
1118 1118
1119 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 1123 if resp.executed:
1123 1124
@@ -821,7 +821,7 b' def is_svn_without_proxy(repository):'
821 821
822 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 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 1016 # PERMS
1017 1017 #==============================================================================
1018 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1019 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1020 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1021 csrf_token_key
1018 from rhodecode.lib.auth import (
1019 HasPermissionAny, HasPermissionAll,
1020 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
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 4401 TYPE_REGISTRATION = u'registration'
4402 4402 TYPE_PULL_REQUEST = u'pull_request'
4403 4403 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4404 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4404 4405
4405 4406 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4406 4407 subject = Column('subject', Unicode(512), nullable=True)
@@ -293,6 +293,7 b' class EmailNotificationModel(BaseModel):'
293 293 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
294 294 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
295 295 TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
296 TYPE_PULL_REQUEST_UPDATE = Notification.TYPE_PULL_REQUEST_UPDATE
296 297 TYPE_MAIN = Notification.TYPE_MESSAGE
297 298
298 299 TYPE_PASSWORD_RESET = 'password_reset'
@@ -319,6 +320,8 b' class EmailNotificationModel(BaseModel):'
319 320 'rhodecode:templates/email_templates/pull_request_review.mako',
320 321 TYPE_PULL_REQUEST_COMMENT:
321 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 327 def __init__(self):
@@ -341,6 +344,7 b' class EmailNotificationModel(BaseModel):'
341 344 """
342 345
343 346 kwargs['rhodecode_instance_name'] = self.rhodecode_instance_name
347 kwargs['rhodecode_version'] = rhodecode.__version__
344 348 instance_url = h.route_url('home')
345 349 _kwargs = {
346 350 'instance_url': instance_url,
@@ -65,9 +65,19 b' log = logging.getLogger(__name__)'
65 65
66 66 # Data structure to hold the response data when updating commits during a pull
67 67 # request update.
68 UpdateResponse = collections.namedtuple('UpdateResponse', [
69 'executed', 'reason', 'new', 'old', 'changes',
70 'source_changed', 'target_changed'])
68 class UpdateResponse(object):
69
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 83 class PullRequestModel(BaseModel):
@@ -672,11 +682,13 b' class PullRequestModel(BaseModel):'
672 682 source_ref_type = pull_request.source_ref_parts.type
673 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 687 Get the updated list of commits for the pull request
678 688 and return the new pull request version and the list
679 689 of commits processed by this update action
690
691 updating_user is the user_object who triggered the update
680 692 """
681 693 pull_request = self.__get_pull_request(pull_request)
682 694 source_ref_type = pull_request.source_ref_parts.type
@@ -693,7 +705,7 b' class PullRequestModel(BaseModel):'
693 705 return UpdateResponse(
694 706 executed=False,
695 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 709 source_changed=False, target_changed=False)
698 710
699 711 # source repo
@@ -705,7 +717,7 b' class PullRequestModel(BaseModel):'
705 717 return UpdateResponse(
706 718 executed=False,
707 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 721 source_changed=False, target_changed=False)
710 722
711 723 source_changed = source_ref_id != source_commit.raw_id
@@ -719,7 +731,7 b' class PullRequestModel(BaseModel):'
719 731 return UpdateResponse(
720 732 executed=False,
721 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 735 source_changed=False, target_changed=False)
724 736 target_changed = target_ref_id != target_commit.raw_id
725 737
@@ -728,7 +740,7 b' class PullRequestModel(BaseModel):'
728 740 return UpdateResponse(
729 741 executed=False,
730 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 744 source_changed=target_changed, target_changed=source_changed)
733 745
734 746 change_in_found = 'target repo' if target_changed else 'source repo'
@@ -759,7 +771,7 b' class PullRequestModel(BaseModel):'
759 771 return UpdateResponse(
760 772 executed=False,
761 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 775 source_changed=source_changed, target_changed=target_changed)
764 776
765 777 # re-compute commit ids
@@ -769,13 +781,13 b' class PullRequestModel(BaseModel):'
769 781 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
770 782 pre_load=pre_load)
771 783
772 ancestor = source_repo.get_common_ancestor(
784 ancestor_commit_id = source_repo.get_common_ancestor(
773 785 source_commit.raw_id, target_commit.raw_id, target_repo)
774 786
775 787 pull_request.source_ref = '%s:%s:%s' % (
776 788 source_ref_type, source_ref_name, source_commit.raw_id)
777 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 792 pull_request.revisions = [
781 793 commit.raw_id for commit in reversed(commit_ranges)]
@@ -787,7 +799,7 b' class PullRequestModel(BaseModel):'
787 799 pull_request, pull_request_version)
788 800
789 801 # calculate commit and file changes
790 changes = self._calculate_commit_id_changes(
802 commit_changes = self._calculate_commit_id_changes(
791 803 old_commit_ids, new_commit_ids)
792 804 file_changes = self._calculate_file_changes(
793 805 old_diff_data, new_diff_data)
@@ -797,23 +809,23 b' class PullRequestModel(BaseModel):'
797 809 pull_request, old_diff_data=old_diff_data,
798 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 813 file_node_changes = (
802 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 817 # Add an automatic comment to the pull request, in case
806 818 # anything has changed
807 819 if pr_has_changes:
808 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 822 repo=pull_request.target_repo,
811 823 user=pull_request.author,
812 824 pull_request=pull_request,
813 825 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
814 826
815 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 829 ChangesetStatusModel().set_status(
818 830 repo=pull_request.source_repo,
819 831 status=ChangesetStatus.STATUS_UNDER_REVIEW,
@@ -822,10 +834,19 b' class PullRequestModel(BaseModel):'
822 834 pull_request=pull_request,
823 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 846 log.debug(
826 847 'Updated pull request %s, added_ids: %s, common_ids: %s, '
827 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 850 log.debug(
830 851 'Updated pull request with the following file changes: %s',
831 852 file_changes)
@@ -841,7 +862,8 b' class PullRequestModel(BaseModel):'
841 862
842 863 return UpdateResponse(
843 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 867 source_changed=source_changed, target_changed=target_changed)
846 868
847 869 def _create_version_from_snapshot(self, pull_request):
@@ -963,12 +985,13 b' class PullRequestModel(BaseModel):'
963 985
964 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 990 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
969 991 so it's always looking the same disregarding on which default
970 992 renderer system is using.
971 993
994 :param ancestor_commit_id: ancestor raw_id
972 995 :param changes: changes named tuple
973 996 :param file_changes: file changes named tuple
974 997
@@ -987,6 +1010,7 b' class PullRequestModel(BaseModel):'
987 1010 'added_files': file_changes.added,
988 1011 'modified_files': file_changes.modified,
989 1012 'removed_files': file_changes.removed,
1013 'ancestor_commit_id': ancestor_commit_id
990 1014 }
991 1015 renderer = RstTemplateRenderer()
992 1016 return renderer.render('pull_request_update.mako', **params)
@@ -1170,6 +1194,75 b' class PullRequestModel(BaseModel):'
1170 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 1266 def delete(self, pull_request, user):
1174 1267 pull_request = self.__get_pull_request(pull_request)
1175 1268 old_data = pull_request.get_api_data(with_merge_state=False)
@@ -115,17 +115,17 b' data = {'
115 115 <td style="width:100%;border-bottom:1px solid #dbd9da;">
116 116
117 117 <h4 style="margin: 0">
118 <div style="margin-bottom: 4px; color:#7E7F7F">
119 @${h.person(user.username)}
118 <div style="margin-bottom: 4px">
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 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 129 <div style="margin-top: 10px"></div>
130 130 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
131 131 </h4>
@@ -78,13 +78,13 b' data = {'
78 78 <td style="width:100%;border-bottom:1px solid #dbd9da;">
79 79
80 80 <h4 style="margin: 0">
81 <div style="margin-bottom: 4px; color:#7E7F7F">
82 @${h.person(user.username)}
81 <div style="margin-bottom: 4px">
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 87 </div>
84 ${_('requested a')}
85 <a href="${pull_request_url}" style="${base.link_css()}">
86 ${_('pull request review.').format(**data) }
87 </a>
88 88 <div style="margin-top: 10px"></div>
89 89 ${_('Pull request')} <code>!${data['pr_id']}: ${data['pr_title']}</code>
90 90 </h4>
@@ -14,13 +14,13 b' Pull request updated. Auto status change'
14 14 %else:
15 15 Changed files:
16 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 18 %endfor
19 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 21 %endfor
22 22 %for file_name in removed_files:
23 * R ${file_name}
23 * `R ${file_name}`
24 24 %endfor
25 25 %endif
26 26
@@ -87,6 +87,59 b' def test_render_pr_email(app, user_admin'
87 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 143 @pytest.mark.parametrize('mention', [
91 144 True,
92 145 False
@@ -538,6 +538,7 b' Pull request updated. Auto status change'
538 538 'added_files': [],
539 539 'modified_files': [],
540 540 'removed_files': [],
541 'ancestor_commit_id': 'aaabbbcccdddeee',
541 542 }
542 543 renderer = RstTemplateRenderer()
543 544 rendered = renderer.render('pull_request_update.mako', **params)
@@ -557,11 +558,11 b' Pull request updated. Auto status change'
557 558 * :removed:`3 removed`
558 559
559 560 Changed files:
560 * `A /path/a.py <#a_c--68ed34923b68>`_
561 * `A /path/b.js <#a_c--64f90608b607>`_
562 * `M /path/d.js <#a_c--85842bf30c6e>`_
563 * `M /path/ę.py <#a_c--d713adf009cd>`_
564 * R /path/ź.py
561 * `A /path/a.py <#a_c-aaabbbcccddd-68ed34923b68>`_
562 * `A /path/b.js <#a_c-aaabbbcccddd-64f90608b607>`_
563 * `M /path/d.js <#a_c-aaabbbcccddd-85842bf30c6e>`_
564 * `M /path/ę.py <#a_c-aaabbbcccddd-d713adf009cd>`_
565 * `R /path/ź.py`
565 566
566 567 .. |under_review| replace:: *"NEW STATUS"*'''
567 568
@@ -577,6 +578,7 b' Pull request updated. Auto status change'
577 578 'added_files': added,
578 579 'modified_files': modified,
579 580 'removed_files': removed,
581 'ancestor_commit_id': 'aaabbbcccdddeee',
580 582 }
581 583 renderer = RstTemplateRenderer()
582 584 rendered = renderer.render('pull_request_update.mako', **params)
@@ -81,7 +81,7 b' def test_pull_request_stays_if_update_wi'
81 81 voted_status, *pull_request.reviewers)
82 82
83 83 # Update, without change
84 PullRequestModel().update_commits(pull_request)
84 PullRequestModel().update_commits(pull_request, pull_request.author)
85 85
86 86 # Expect that review status is the voted_status
87 87 expected_review_status = voted_status
@@ -100,7 +100,7 b' def test_pull_request_under_review_if_up'
100 100
101 101 # Update, with change
102 102 pr_util.update_source_repository()
103 PullRequestModel().update_commits(pull_request)
103 PullRequestModel().update_commits(pull_request, pull_request.author)
104 104
105 105 # Expect that review status is the voted_status
106 106 expected_review_status = db.ChangesetStatus.STATUS_UNDER_REVIEW
@@ -171,7 +171,7 b' def test_commit_keeps_status_if_unchange'
171 171 commit_id = pull_request.revisions[-1]
172 172 pr_util.create_status_votes(voted_status, pull_request.reviewers[0])
173 173 pr_util.update_source_repository()
174 PullRequestModel().update_commits(pull_request)
174 PullRequestModel().update_commits(pull_request, pull_request.author)
175 175 assert pull_request.revisions[-1] == commit_id
176 176
177 177 status = ChangesetStatusModel().get_status(
@@ -814,7 +814,7 b' def test_update_writes_snapshot_into_pul'
814 814 pull_request = pr_util.create_pull_request()
815 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 819 # Expect that it has a version entry now
820 820 assert len(model.get_versions(pull_request)) == 1
@@ -823,7 +823,7 b' def test_update_writes_snapshot_into_pul'
823 823 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
824 824 pull_request = pr_util.create_pull_request()
825 825 model = PullRequestModel()
826 model.update_commits(pull_request)
826 model.update_commits(pull_request, pull_request.author)
827 827
828 828 # Expect that it still has no versions
829 829 assert len(model.get_versions(pull_request)) == 0
@@ -835,7 +835,7 b' def test_update_assigns_comments_to_the_'
835 835 comment = pr_util.create_comment()
836 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 840 # Expect that the comment is linked to the pr version now
841 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 847 pr_util.update_source_repository()
848 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 853 # Expect to find a new comment about the change
853 854 expected_message = textwrap.dedent(
854 855 """\
@@ -863,10 +864,10 b' def test_update_adds_a_comment_to_the_pu'
863 864 * :removed:`0 removed`
864 865
865 866 Changed files:
866 * `A file_2 <#a_c--92ed3b5f07b4>`_
867 * `A file_2 <#a_c-{}-92ed3b5f07b4>`_
867 868
868 869 .. |under_review| replace:: *"Under Review"*"""
869 )
870 ).format(commit_id[:12])
870 871 pull_request_comments = sorted(
871 872 pull_request.comments, key=lambda c: c.modified_at)
872 873 update_comment = pull_request_comments[-1]
@@ -1047,7 +1047,7 b' class PRTestUtility(object):'
1047 1047 def add_one_commit(self, head=None):
1048 1048 self.update_source_repository(head=head)
1049 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 1051 commit_ids = set(self.pull_request.revisions)
1052 1052 new_commit_ids = commit_ids - old_commit_ids
1053 1053 assert len(new_commit_ids) == 1
@@ -1066,7 +1066,7 b' class PRTestUtility(object):'
1066 1066 kwargs = {}
1067 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 1070 assert len(self.pull_request.revisions) == 1
1071 1071 return removed_commit_id
1072 1072
General Comments 0
You need to be logged in to leave comments. Login now