##// END OF EJS Templates
pull-requests: overhaul of the UX by adding new sidebar...
marcink -
r4482:3b004b10 default
parent child
Show More
@@ -345,6 +345,16 def includeme(config):
345 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
345 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
346 repo_route=True, repo_accepted_types=['hg', 'git'])
346 repo_route=True, repo_accepted_types=['hg', 'git'])
347
347
348 config.add_route(
349 name='pullrequest_comments',
350 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments',
351 repo_route=True)
352
353 config.add_route(
354 name='pullrequest_todos',
355 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos',
356 repo_route=True)
357
348 # Artifacts, (EE feature)
358 # Artifacts, (EE feature)
349 config.add_route(
359 config.add_route(
350 name='repo_artifacts_list',
360 name='repo_artifacts_list',
@@ -87,6 +87,7 class RepoCommitsView(RepoAppView):
87 diff_limit = c.visual.cut_off_limit_diff
87 diff_limit = c.visual.cut_off_limit_diff
88 file_limit = c.visual.cut_off_limit_file
88 file_limit = c.visual.cut_off_limit_file
89
89
90
90 # get ranges of commit ids if preset
91 # get ranges of commit ids if preset
91 commit_range = commit_id_range.split('...')[:2]
92 commit_range = commit_id_range.split('...')[:2]
92
93
@@ -226,6 +227,7 class RepoCommitsView(RepoAppView):
226
227
227 # sort comments by how they were generated
228 # sort comments by how they were generated
228 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
229 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
230 c.at_version_num = None
229
231
230 if len(c.commit_ranges) == 1:
232 if len(c.commit_ranges) == 1:
231 c.commit = c.commit_ranges[0]
233 c.commit = c.commit_ranges[0]
@@ -265,6 +265,36 class RepoPullRequestsView(RepoAppView,
265
265
266 return diffset
266 return diffset
267
267
268 def register_comments_vars(self, c, pull_request, versions):
269 comments_model = CommentsModel()
270
271 # GENERAL COMMENTS with versions #
272 q = comments_model._all_general_comments_of_pull_request(pull_request)
273 q = q.order_by(ChangesetComment.comment_id.asc())
274 general_comments = q
275
276 # pick comments we want to render at current version
277 c.comment_versions = comments_model.aggregate_comments(
278 general_comments, versions, c.at_version_num)
279
280 # INLINE COMMENTS with versions #
281 q = comments_model._all_inline_comments_of_pull_request(pull_request)
282 q = q.order_by(ChangesetComment.comment_id.asc())
283 inline_comments = q
284
285 c.inline_versions = comments_model.aggregate_comments(
286 inline_comments, versions, c.at_version_num, inline=True)
287
288 # Comments inline+general
289 if c.at_version:
290 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
291 c.comments = c.comment_versions[c.at_version_num]['display']
292 else:
293 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
294 c.comments = c.comment_versions[c.at_version_num]['until']
295
296 return general_comments, inline_comments
297
268 @LoginRequired()
298 @LoginRequired()
269 @HasRepoPermissionAnyDecorator(
299 @HasRepoPermissionAnyDecorator(
270 'repository.read', 'repository.write', 'repository.admin')
300 'repository.read', 'repository.write', 'repository.admin')
@@ -280,6 +310,8 class RepoPullRequestsView(RepoAppView,
280 pull_request_id = pull_request.pull_request_id
310 pull_request_id = pull_request.pull_request_id
281
311
282 c.state_progressing = pull_request.is_state_changing()
312 c.state_progressing = pull_request.is_state_changing()
313 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
314 pull_request.target_repo.repo_name, pull_request.pull_request_id)
283
315
284 _new_state = {
316 _new_state = {
285 'created': PullRequest.STATE_CREATED,
317 'created': PullRequest.STATE_CREATED,
@@ -300,22 +332,23 class RepoPullRequestsView(RepoAppView,
300 from_version = self.request.GET.get('from_version') or version
332 from_version = self.request.GET.get('from_version') or version
301 merge_checks = self.request.GET.get('merge_checks')
333 merge_checks = self.request.GET.get('merge_checks')
302 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
303
337
304 # fetch global flags of ignore ws or context lines
338 # fetch global flags of ignore ws or context lines
305 diff_context = diffs.get_diff_context(self.request)
339 diff_context = diffs.get_diff_context(self.request)
306 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
307
341
308 force_refresh = str2bool(self.request.GET.get('force_refresh'))
309
310 (pull_request_latest,
342 (pull_request_latest,
311 pull_request_at_ver,
343 pull_request_at_ver,
312 pull_request_display_obj,
344 pull_request_display_obj,
313 at_version) = PullRequestModel().get_pr_version(
345 at_version) = PullRequestModel().get_pr_version(
314 pull_request_id, version=version)
346 pull_request_id, version=version)
347
315 pr_closed = pull_request_latest.is_closed()
348 pr_closed = pull_request_latest.is_closed()
316
349
317 if pr_closed and (version or from_version):
350 if pr_closed and (version or from_version):
318 # not allow to browse versions
351 # not allow to browse versions for closed PR
319 raise HTTPFound(h.route_path(
352 raise HTTPFound(h.route_path(
320 'pullrequest_show', repo_name=self.db_repo_name,
353 'pullrequest_show', repo_name=self.db_repo_name,
321 pull_request_id=pull_request_id))
354 pull_request_id=pull_request_id))
@@ -323,13 +356,13 class RepoPullRequestsView(RepoAppView,
323 versions = pull_request_display_obj.versions()
356 versions = pull_request_display_obj.versions()
324 # used to store per-commit range diffs
357 # used to store per-commit range diffs
325 c.changes = collections.OrderedDict()
358 c.changes = collections.OrderedDict()
326 c.range_diff_on = self.request.GET.get('range-diff') == "1"
327
359
328 c.at_version = at_version
360 c.at_version = at_version
329 c.at_version_num = (at_version
361 c.at_version_num = (at_version
330 if at_version and at_version != 'latest'
362 if at_version and at_version != PullRequest.LATEST_VER
331 else None)
363 else None)
332 c.at_version_pos = ChangesetComment.get_index_from_version(
364
365 c.at_version_index = ChangesetComment.get_index_from_version(
333 c.at_version_num, versions)
366 c.at_version_num, versions)
334
367
335 (prev_pull_request_latest,
368 (prev_pull_request_latest,
@@ -340,9 +373,9 class RepoPullRequestsView(RepoAppView,
340
373
341 c.from_version = prev_at_version
374 c.from_version = prev_at_version
342 c.from_version_num = (prev_at_version
375 c.from_version_num = (prev_at_version
343 if prev_at_version and prev_at_version != 'latest'
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
344 else None)
377 else None)
345 c.from_version_pos = ChangesetComment.get_index_from_version(
378 c.from_version_index = ChangesetComment.get_index_from_version(
346 c.from_version_num, versions)
379 c.from_version_num, versions)
347
380
348 # define if we're in COMPARE mode or VIEW at version mode
381 # define if we're in COMPARE mode or VIEW at version mode
@@ -355,14 +388,17 class RepoPullRequestsView(RepoAppView,
355 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
356 raise HTTPNotFound()
389 raise HTTPNotFound()
357
390
358 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
359 pull_request_at_ver)
360
392
361 c.pull_request = pull_request_display_obj
393 c.pull_request = pull_request_display_obj
362 c.renderer = pull_request_at_ver.description_renderer or c.renderer
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
363 c.pull_request_latest = pull_request_latest
395 c.pull_request_latest = pull_request_latest
364
396
365 if compare or (at_version and not at_version == 'latest'):
397 # inject latest version
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
399 c.versions = versions + [latest_ver]
400
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
366 c.allowed_to_change_status = False
402 c.allowed_to_change_status = False
367 c.allowed_to_update = False
403 c.allowed_to_update = False
368 c.allowed_to_merge = False
404 c.allowed_to_merge = False
@@ -391,12 +427,9 class RepoPullRequestsView(RepoAppView,
391 'rules' in pull_request_latest.reviewer_data:
427 'rules' in pull_request_latest.reviewer_data:
392 rules = pull_request_latest.reviewer_data['rules'] or {}
428 rules = pull_request_latest.reviewer_data['rules'] or {}
393 try:
429 try:
394 c.forbid_adding_reviewers = rules.get(
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
395 'forbid_adding_reviewers')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
396 c.forbid_author_to_review = rules.get(
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
397 'forbid_author_to_review')
398 c.forbid_commit_author_to_review = rules.get(
399 'forbid_commit_author_to_review')
400 except Exception:
433 except Exception:
401 pass
434 pass
402
435
@@ -421,41 +454,37 class RepoPullRequestsView(RepoAppView,
421 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
422 return self._get_template_context(c)
455 return self._get_template_context(c)
423
456
424 comments_model = CommentsModel()
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
425
458
426 # reviewers and statuses
459 # reviewers and statuses
427 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
460 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
428 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
461 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
429
462
430 # GENERAL COMMENTS with versions #
463 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
431 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
464 member_reviewer = h.reviewer_as_json(
432 q = q.order_by(ChangesetComment.comment_id.asc())
465 member, reasons=reasons, mandatory=mandatory,
433 general_comments = q
466 user_group=review_obj.rule_user_group_data()
467 )
434
468
435 # pick comments we want to render at current version
469 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
436 c.comment_versions = comments_model.aggregate_comments(
470 member_reviewer['review_status'] = current_review_status
437 general_comments, versions, c.at_version_num)
471 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
438 c.comments = c.comment_versions[c.at_version_num]['until']
472 member_reviewer['allowed_to_update'] = c.allowed_to_update
473 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
439
474
440 # INLINE COMMENTS with versions #
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
441 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
476
442 q = q.order_by(ChangesetComment.comment_id.asc())
477
443 inline_comments = q
444
478
445 c.inline_versions = comments_model.aggregate_comments(
479
446 inline_comments, versions, c.at_version_num, inline=True)
480 general_comments, inline_comments = \
481 self.register_comments_vars(c, pull_request_latest, versions)
447
482
448 # TODOs
483 # TODOs
449 c.unresolved_comments = CommentsModel() \
484 c.unresolved_comments = CommentsModel() \
450 .get_pull_request_unresolved_todos(pull_request)
485 .get_pull_request_unresolved_todos(pull_request_latest)
451 c.resolved_comments = CommentsModel() \
486 c.resolved_comments = CommentsModel() \
452 .get_pull_request_resolved_todos(pull_request)
487 .get_pull_request_resolved_todos(pull_request_latest)
453
454 # inject latest version
455 latest_ver = PullRequest.get_pr_display_object(
456 pull_request_latest, pull_request_latest)
457
458 c.versions = versions + [latest_ver]
459
488
460 # if we use version, then do not show later comments
489 # if we use version, then do not show later comments
461 # than current version
490 # than current version
@@ -522,8 +551,8 class RepoPullRequestsView(RepoAppView,
522
551
523 # empty version means latest, so we keep this to prevent
552 # empty version means latest, so we keep this to prevent
524 # double caching
553 # double caching
525 version_normalized = version or 'latest'
554 version_normalized = version or PullRequest.LATEST_VER
526 from_version_normalized = from_version or 'latest'
555 from_version_normalized = from_version or PullRequest.LATEST_VER
527
556
528 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
557 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
529 cache_file_path = diff_cache_exist(
558 cache_file_path = diff_cache_exist(
@@ -615,7 +644,7 class RepoPullRequestsView(RepoAppView,
615 diff_limit, file_limit, c.fulldiff,
644 diff_limit, file_limit, c.fulldiff,
616 hide_whitespace_changes, diff_context,
645 hide_whitespace_changes, diff_context,
617 use_ancestor=use_ancestor
646 use_ancestor=use_ancestor
618 )
647 )
619
648
620 # save cached diff
649 # save cached diff
621 if caching_enabled:
650 if caching_enabled:
@@ -719,7 +748,7 class RepoPullRequestsView(RepoAppView,
719
748
720 # current user review statuses for each version
749 # current user review statuses for each version
721 c.review_versions = {}
750 c.review_versions = {}
722 if self._rhodecode_user.user_id in allowed_reviewers:
751 if self._rhodecode_user.user_id in c.allowed_reviewers:
723 for co in general_comments:
752 for co in general_comments:
724 if co.author.user_id == self._rhodecode_user.user_id:
753 if co.author.user_id == self._rhodecode_user.user_id:
725 status = co.status_change
754 status = co.status_change
@@ -939,6 +968,83 class RepoPullRequestsView(RepoAppView,
939 @NotAnonymous()
968 @NotAnonymous()
940 @HasRepoPermissionAnyDecorator(
969 @HasRepoPermissionAnyDecorator(
941 'repository.read', 'repository.write', 'repository.admin')
970 'repository.read', 'repository.write', 'repository.admin')
971 @view_config(
972 route_name='pullrequest_comments', request_method='POST',
973 renderer='string', xhr=True)
974 def pullrequest_comments(self):
975 self.load_default_context()
976
977 pull_request = PullRequest.get_or_404(
978 self.request.matchdict['pull_request_id'])
979 pull_request_id = pull_request.pull_request_id
980 version = self.request.GET.get('version')
981
982 _render = self.request.get_partial_renderer(
983 'rhodecode:templates/pullrequests/pullrequest_show.mako')
984 c = _render.get_call_context()
985
986 (pull_request_latest,
987 pull_request_at_ver,
988 pull_request_display_obj,
989 at_version) = PullRequestModel().get_pr_version(
990 pull_request_id, version=version)
991 versions = pull_request_display_obj.versions()
992 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
993 c.versions = versions + [latest_ver]
994
995 c.at_version = at_version
996 c.at_version_num = (at_version
997 if at_version and at_version != PullRequest.LATEST_VER
998 else None)
999
1000 self.register_comments_vars(c, pull_request_latest, versions)
1001 all_comments = c.inline_comments_flat + c.comments
1002 return _render('comments_table', all_comments, len(all_comments))
1003
1004 @LoginRequired()
1005 @NotAnonymous()
1006 @HasRepoPermissionAnyDecorator(
1007 'repository.read', 'repository.write', 'repository.admin')
1008 @view_config(
1009 route_name='pullrequest_todos', request_method='POST',
1010 renderer='string', xhr=True)
1011 def pullrequest_todos(self):
1012 self.load_default_context()
1013
1014 pull_request = PullRequest.get_or_404(
1015 self.request.matchdict['pull_request_id'])
1016 pull_request_id = pull_request.pull_request_id
1017 version = self.request.GET.get('version')
1018
1019 _render = self.request.get_partial_renderer(
1020 'rhodecode:templates/pullrequests/pullrequest_show.mako')
1021 c = _render.get_call_context()
1022 (pull_request_latest,
1023 pull_request_at_ver,
1024 pull_request_display_obj,
1025 at_version) = PullRequestModel().get_pr_version(
1026 pull_request_id, version=version)
1027 versions = pull_request_display_obj.versions()
1028 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1029 c.versions = versions + [latest_ver]
1030
1031 c.at_version = at_version
1032 c.at_version_num = (at_version
1033 if at_version and at_version != PullRequest.LATEST_VER
1034 else None)
1035
1036 c.unresolved_comments = CommentsModel() \
1037 .get_pull_request_unresolved_todos(pull_request)
1038 c.resolved_comments = CommentsModel() \
1039 .get_pull_request_resolved_todos(pull_request)
1040
1041 all_comments = c.unresolved_comments + c.resolved_comments
1042 return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True)
1043
1044 @LoginRequired()
1045 @NotAnonymous()
1046 @HasRepoPermissionAnyDecorator(
1047 'repository.read', 'repository.write', 'repository.admin')
942 @CSRFRequired()
1048 @CSRFRequired()
943 @view_config(
1049 @view_config(
944 route_name='pullrequest_create', request_method='POST',
1050 route_name='pullrequest_create', request_method='POST',
@@ -1100,7 +1206,7 class RepoPullRequestsView(RepoAppView,
1100 self.request.matchdict['pull_request_id'])
1206 self.request.matchdict['pull_request_id'])
1101 _ = self.request.translate
1207 _ = self.request.translate
1102
1208
1103 self.load_default_context()
1209 c = self.load_default_context()
1104 redirect_url = None
1210 redirect_url = None
1105
1211
1106 if pull_request.is_closed():
1212 if pull_request.is_closed():
@@ -1111,6 +1217,8 class RepoPullRequestsView(RepoAppView,
1111 'redirect_url': redirect_url}
1217 'redirect_url': redirect_url}
1112
1218
1113 is_state_changing = pull_request.is_state_changing()
1219 is_state_changing = pull_request.is_state_changing()
1220 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
1221 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1114
1222
1115 # only owner or admin can update it
1223 # only owner or admin can update it
1116 allowed_to_update = PullRequestModel().check_user_update(
1224 allowed_to_update = PullRequestModel().check_user_update(
@@ -1134,7 +1242,7 class RepoPullRequestsView(RepoAppView,
1134 return {'response': True,
1242 return {'response': True,
1135 'redirect_url': redirect_url}
1243 'redirect_url': redirect_url}
1136
1244
1137 self._update_commits(pull_request)
1245 self._update_commits(c, pull_request)
1138 if force_refresh:
1246 if force_refresh:
1139 redirect_url = h.route_path(
1247 redirect_url = h.route_path(
1140 'pullrequest_show', repo_name=self.db_repo_name,
1248 'pullrequest_show', repo_name=self.db_repo_name,
@@ -1170,7 +1278,7 class RepoPullRequestsView(RepoAppView,
1170 h.flash(msg, category='success')
1278 h.flash(msg, category='success')
1171 return
1279 return
1172
1280
1173 def _update_commits(self, pull_request):
1281 def _update_commits(self, c, pull_request):
1174 _ = self.request.translate
1282 _ = self.request.translate
1175
1283
1176 with pull_request.set_state(PullRequest.STATE_UPDATING):
1284 with pull_request.set_state(PullRequest.STATE_UPDATING):
@@ -1198,13 +1306,18 class RepoPullRequestsView(RepoAppView,
1198 change_source=changed)
1306 change_source=changed)
1199 h.flash(msg, category='success')
1307 h.flash(msg, category='success')
1200
1308
1201 channel = '/repo${}$/pr/{}'.format(
1202 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1203 message = msg + (
1309 message = msg + (
1204 ' - <a onclick="window.location.reload()">'
1310 ' - <a onclick="window.location.reload()">'
1205 '<strong>{}</strong></a>'.format(_('Reload page')))
1311 '<strong>{}</strong></a>'.format(_('Reload page')))
1312
1313 message_obj = {
1314 'message': message,
1315 'level': 'success',
1316 'topic': '/notifications'
1317 }
1318
1206 channelstream.post_message(
1319 channelstream.post_message(
1207 channel, message, self._rhodecode_user.username,
1320 c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
1208 registry=self.request.registry)
1321 registry=self.request.registry)
1209 else:
1322 else:
1210 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1323 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
@@ -1474,6 +1587,7 class RepoPullRequestsView(RepoAppView,
1474 }
1587 }
1475 if comment:
1588 if comment:
1476 c.co = comment
1589 c.co = comment
1590 c.at_version_num = None
1477 rendered_comment = render(
1591 rendered_comment = render(
1478 'rhodecode:templates/changeset/changeset_comment_block.mako',
1592 'rhodecode:templates/changeset/changeset_comment_block.mako',
1479 self._get_template_context(c), self.request)
1593 self._get_template_context(c), self.request)
@@ -810,8 +810,7 import tzlocal
810 local_timezone = tzlocal.get_localzone()
810 local_timezone = tzlocal.get_localzone()
811
811
812
812
813 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
813 def get_timezone(datetime_iso, time_is_local=False):
814 title = value or format_date(datetime_iso)
815 tzinfo = '+00:00'
814 tzinfo = '+00:00'
816
815
817 # detect if we have a timezone info, otherwise, add it
816 # detect if we have a timezone info, otherwise, add it
@@ -822,6 +821,12 def age_component(datetime_iso, value=No
822 timezone = force_timezone or local_timezone
821 timezone = force_timezone or local_timezone
823 offset = timezone.localize(datetime_iso).strftime('%z')
822 offset = timezone.localize(datetime_iso).strftime('%z')
824 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
823 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
824 return tzinfo
825
826
827 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
828 title = value or format_date(datetime_iso)
829 tzinfo = get_timezone(datetime_iso, time_is_local=time_is_local)
825
830
826 return literal(
831 return literal(
827 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
832 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
@@ -1650,17 +1655,18 def get_active_pattern_entries(repo_name
1650
1655
1651 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1656 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1652
1657
1658 allowed_link_formats = [
1659 'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1660
1653
1661
1654 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1662 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1655
1663
1656 allowed_formats = ['html', 'rst', 'markdown',
1664 if link_format not in allowed_link_formats:
1657 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1658 if link_format not in allowed_formats:
1659 raise ValueError('Link format can be only one of:{} got {}'.format(
1665 raise ValueError('Link format can be only one of:{} got {}'.format(
1660 allowed_formats, link_format))
1666 allowed_link_formats, link_format))
1661
1667
1662 if active_entries is None:
1668 if active_entries is None:
1663 log.debug('Fetch active patterns for repo: %s', repo_name)
1669 log.debug('Fetch active issue tracker patterns for repo: %s', repo_name)
1664 active_entries = get_active_pattern_entries(repo_name)
1670 active_entries = get_active_pattern_entries(repo_name)
1665
1671
1666 issues_data = []
1672 issues_data = []
@@ -1718,7 +1724,8 def process_patterns(text_string, repo_n
1718 return new_text, issues_data
1724 return new_text, issues_data
1719
1725
1720
1726
1721 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1727 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None,
1728 issues_container=None):
1722 """
1729 """
1723 Parses given text message and makes proper links.
1730 Parses given text message and makes proper links.
1724 issues are linked to given issue-server, and rest is a commit link
1731 issues are linked to given issue-server, and rest is a commit link
@@ -1741,6 +1748,9 def urlify_commit_message(commit_text, r
1741 new_text, issues = process_patterns(new_text, repository or '',
1748 new_text, issues = process_patterns(new_text, repository or '',
1742 active_entries=active_pattern_entries)
1749 active_entries=active_pattern_entries)
1743
1750
1751 if issues_container is not None:
1752 issues_container.extend(issues)
1753
1744 return literal(new_text)
1754 return literal(new_text)
1745
1755
1746
1756
@@ -1781,7 +1791,7 def renderer_from_filename(filename, exc
1781
1791
1782
1792
1783 def render(source, renderer='rst', mentions=False, relative_urls=None,
1793 def render(source, renderer='rst', mentions=False, relative_urls=None,
1784 repo_name=None, active_pattern_entries=None):
1794 repo_name=None, active_pattern_entries=None, issues_container=None):
1785
1795
1786 def maybe_convert_relative_links(html_source):
1796 def maybe_convert_relative_links(html_source):
1787 if relative_urls:
1797 if relative_urls:
@@ -1798,6 +1808,8 def render(source, renderer='rst', menti
1798 source, issues = process_patterns(
1808 source, issues = process_patterns(
1799 source, repo_name, link_format='rst',
1809 source, repo_name, link_format='rst',
1800 active_entries=active_pattern_entries)
1810 active_entries=active_pattern_entries)
1811 if issues_container is not None:
1812 issues_container.extend(issues)
1801
1813
1802 return literal(
1814 return literal(
1803 '<div class="rst-block">%s</div>' %
1815 '<div class="rst-block">%s</div>' %
@@ -1810,6 +1822,8 def render(source, renderer='rst', menti
1810 source, issues = process_patterns(
1822 source, issues = process_patterns(
1811 source, repo_name, link_format='markdown',
1823 source, repo_name, link_format='markdown',
1812 active_entries=active_pattern_entries)
1824 active_entries=active_pattern_entries)
1825 if issues_container is not None:
1826 issues_container.extend(issues)
1813
1827
1814 return literal(
1828 return literal(
1815 '<div class="markdown-block">%s</div>' %
1829 '<div class="markdown-block">%s</div>' %
@@ -91,8 +91,7 class CommentsModel(BaseModel):
91 # group by versions, and count until, and display objects
91 # group by versions, and count until, and display objects
92
92
93 comment_groups = collections.defaultdict(list)
93 comment_groups = collections.defaultdict(list)
94 [comment_groups[
94 [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
95 _co.pull_request_version_id].append(_co) for _co in comments]
96
95
97 def yield_comments(pos):
96 def yield_comments(pos):
98 for co in comment_groups[pos]:
97 for co in comment_groups[pos]:
@@ -456,38 +455,54 class CommentsModel(BaseModel):
456 else:
455 else:
457 action = 'repo.commit.comment.create'
456 action = 'repo.commit.comment.create'
458
457
458 comment_id = comment.comment_id
459 comment_data = comment.get_api_data()
459 comment_data = comment.get_api_data()
460
460 self._log_audit_action(
461 self._log_audit_action(
461 action, {'data': comment_data}, auth_user, comment)
462 action, {'data': comment_data}, auth_user, comment)
462
463
463 msg_url = ''
464 channel = None
464 channel = None
465 if commit_obj:
465 if commit_obj:
466 msg_url = commit_comment_url
467 repo_name = repo.repo_name
466 repo_name = repo.repo_name
468 channel = u'/repo${}$/commit/{}'.format(
467 channel = u'/repo${}$/commit/{}'.format(
469 repo_name,
468 repo_name,
470 commit_obj.raw_id
469 commit_obj.raw_id
471 )
470 )
472 elif pull_request_obj:
471 elif pull_request_obj:
473 msg_url = pr_comment_url
474 repo_name = pr_target_repo.repo_name
472 repo_name = pr_target_repo.repo_name
475 channel = u'/repo${}$/pr/{}'.format(
473 channel = u'/repo${}$/pr/{}'.format(
476 repo_name,
474 repo_name,
477 pull_request_id
475 pull_request_obj.pull_request_id
478 )
476 )
479
477
480 message = '<strong>{}</strong> {} - ' \
478 if channel:
481 '<a onclick="window.location=\'{}\';' \
479 username = user.username
482 'window.location.reload()">' \
480 message = '<strong>{}</strong> {} #{}, {}'
483 '<strong>{}</strong></a>'
481 message = message.format(
484 message = message.format(
482 username,
485 user.username, _('made a comment'), msg_url,
483 _('posted a new comment'),
486 _('Show it now'))
484 comment_id,
485 _('Refresh the page to see new comments.'))
487
486
488 channelstream.post_message(
487 message_obj = {
489 channel, message, user.username,
488 'message': message,
490 registry=get_current_registry())
489 'level': 'success',
490 'topic': '/notifications'
491 }
492
493 channelstream.post_message(
494 channel, message_obj, user.username,
495 registry=get_current_registry())
496
497 message_obj = {
498 'message': None,
499 'user': username,
500 'comment_id': comment_id,
501 'topic': '/comment'
502 }
503 channelstream.post_message(
504 channel, message_obj, user.username,
505 registry=get_current_registry())
491
506
492 return comment
507 return comment
493
508
@@ -642,15 +657,15 class CommentsModel(BaseModel):
642 return self._group_comments_by_path_and_line_number(q)
657 return self._group_comments_by_path_and_line_number(q)
643
658
644 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
659 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
645 version=None):
660 version=None):
646 inline_cnt = 0
661 inline_comms = []
647 for fname, per_line_comments in inline_comments.iteritems():
662 for fname, per_line_comments in inline_comments.iteritems():
648 for lno, comments in per_line_comments.iteritems():
663 for lno, comments in per_line_comments.iteritems():
649 for comm in comments:
664 for comm in comments:
650 if not comm.outdated_at_version(version) and skip_outdated:
665 if not comm.outdated_at_version(version) and skip_outdated:
651 inline_cnt += 1
666 inline_comms.append(comm)
652
667
653 return inline_cnt
668 return inline_comms
654
669
655 def get_outdated_comments(self, repo_id, pull_request):
670 def get_outdated_comments(self, repo_id, pull_request):
656 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
671 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
@@ -3821,16 +3821,35 class ChangesetComment(Base, BaseModel):
3821 """
3821 """
3822 Checks if comment is outdated for given pull request version
3822 Checks if comment is outdated for given pull request version
3823 """
3823 """
3824 return self.outdated and self.pull_request_version_id != version
3824 def version_check():
3825 return self.pull_request_version_id and self.pull_request_version_id != version
3826
3827 if self.is_inline:
3828 return self.outdated and version_check()
3829 else:
3830 # general comments don't have .outdated set, also latest don't have a version
3831 return version_check()
3832
3833 def outdated_at_version_js(self, version):
3834 """
3835 Checks if comment is outdated for given pull request version
3836 """
3837 return json.dumps(self.outdated_at_version(version))
3825
3838
3826 def older_than_version(self, version):
3839 def older_than_version(self, version):
3827 """
3840 """
3828 Checks if comment is made from previous version than given
3841 Checks if comment is made from previous version than given
3829 """
3842 """
3830 if version is None:
3843 if version is None:
3831 return self.pull_request_version_id is not None
3844 return self.pull_request_version != version
3832
3845
3833 return self.pull_request_version_id < version
3846 return self.pull_request_version < version
3847
3848 def older_than_version_js(self, version):
3849 """
3850 Checks if comment is made from previous version than given
3851 """
3852 return json.dumps(self.older_than_version(version))
3834
3853
3835 @property
3854 @property
3836 def commit_id(self):
3855 def commit_id(self):
@@ -4327,6 +4346,7 class PullRequest(Base, _PullRequestBase
4327 __table_args__ = (
4346 __table_args__ = (
4328 base_table_args,
4347 base_table_args,
4329 )
4348 )
4349 LATEST_VER = 'latest'
4330
4350
4331 pull_request_id = Column(
4351 pull_request_id = Column(
4332 'pull_request_id', Integer(), nullable=False, primary_key=True)
4352 'pull_request_id', Integer(), nullable=False, primary_key=True)
@@ -4385,6 +4405,10 class PullRequest(Base, _PullRequestBase
4385 def pull_request_version_id(self):
4405 def pull_request_version_id(self):
4386 return getattr(pull_request_obj, 'pull_request_version_id', None)
4406 return getattr(pull_request_obj, 'pull_request_version_id', None)
4387
4407
4408 @property
4409 def pull_request_last_version(self):
4410 return pull_request_obj.pull_request_last_version
4411
4388 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4412 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4389
4413
4390 attrs.author = StrictAttributeDict(
4414 attrs.author = StrictAttributeDict(
@@ -4449,6 +4473,10 class PullRequest(Base, _PullRequestBase
4449 """
4473 """
4450 return self.versions.count() + 1
4474 return self.versions.count() + 1
4451
4475
4476 @property
4477 def pull_request_last_version(self):
4478 return self.versions_count
4479
4452
4480
4453 class PullRequestVersion(Base, _PullRequestBase):
4481 class PullRequestVersion(Base, _PullRequestBase):
4454 __tablename__ = 'pull_request_versions'
4482 __tablename__ = 'pull_request_versions'
@@ -240,14 +240,14 div.markdown-block ol {
240 div.markdown-block ul.checkbox li,
240 div.markdown-block ul.checkbox li,
241 div.markdown-block ol.checkbox li {
241 div.markdown-block ol.checkbox li {
242 list-style: none !important;
242 list-style: none !important;
243 margin: 6px !important;
243 margin: 0px !important;
244 padding: 0 !important;
244 padding: 0 !important;
245 }
245 }
246
246
247 div.markdown-block ul li,
247 div.markdown-block ul li,
248 div.markdown-block ol li {
248 div.markdown-block ol li {
249 list-style: disc !important;
249 list-style: disc !important;
250 margin: 6px !important;
250 margin: 0px !important;
251 padding: 0 !important;
251 padding: 0 !important;
252 }
252 }
253
253
@@ -1510,18 +1510,14 table.integrations {
1510 min-height: 55px;
1510 min-height: 55px;
1511 }
1511 }
1512
1512
1513 .reviewers_member {
1514 width: 100%;
1515 overflow: auto;
1516 }
1517 .reviewer_reason {
1513 .reviewer_reason {
1518 padding-left: 20px;
1514 padding-left: 20px;
1519 line-height: 1.5em;
1515 line-height: 1.5em;
1520 }
1516 }
1521 .reviewer_status {
1517 .reviewer_status {
1522 display: inline-block;
1518 display: inline-block;
1523 width: 25px;
1519 width: 20px;
1524 min-width: 25px;
1520 min-width: 20px;
1525 height: 1.2em;
1521 height: 1.2em;
1526 line-height: 1em;
1522 line-height: 1em;
1527