##// END OF EJS Templates
pull-requests: overhaul of the UX by adding new sidebar...
marcink -
r4482:3b004b10 default
parent child Browse files
Show More
@@ -345,6 +345,16 b' def includeme(config):'
345 345 pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete',
346 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 358 # Artifacts, (EE feature)
349 359 config.add_route(
350 360 name='repo_artifacts_list',
@@ -87,6 +87,7 b' class RepoCommitsView(RepoAppView):'
87 87 diff_limit = c.visual.cut_off_limit_diff
88 88 file_limit = c.visual.cut_off_limit_file
89 89
90
90 91 # get ranges of commit ids if preset
91 92 commit_range = commit_id_range.split('...')[:2]
92 93
@@ -226,6 +227,7 b' class RepoCommitsView(RepoAppView):'
226 227
227 228 # sort comments by how they were generated
228 229 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
230 c.at_version_num = None
229 231
230 232 if len(c.commit_ranges) == 1:
231 233 c.commit = c.commit_ranges[0]
@@ -265,6 +265,36 b' class RepoPullRequestsView(RepoAppView, '
265 265
266 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 298 @LoginRequired()
269 299 @HasRepoPermissionAnyDecorator(
270 300 'repository.read', 'repository.write', 'repository.admin')
@@ -280,6 +310,8 b' class RepoPullRequestsView(RepoAppView, '
280 310 pull_request_id = pull_request.pull_request_id
281 311
282 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 316 _new_state = {
285 317 'created': PullRequest.STATE_CREATED,
@@ -300,22 +332,23 b' class RepoPullRequestsView(RepoAppView, '
300 332 from_version = self.request.GET.get('from_version') or version
301 333 merge_checks = self.request.GET.get('merge_checks')
302 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 338 # fetch global flags of ignore ws or context lines
305 339 diff_context = diffs.get_diff_context(self.request)
306 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 342 (pull_request_latest,
311 343 pull_request_at_ver,
312 344 pull_request_display_obj,
313 345 at_version) = PullRequestModel().get_pr_version(
314 346 pull_request_id, version=version)
347
315 348 pr_closed = pull_request_latest.is_closed()
316 349
317 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 352 raise HTTPFound(h.route_path(
320 353 'pullrequest_show', repo_name=self.db_repo_name,
321 354 pull_request_id=pull_request_id))
@@ -323,13 +356,13 b' class RepoPullRequestsView(RepoAppView, '
323 356 versions = pull_request_display_obj.versions()
324 357 # used to store per-commit range diffs
325 358 c.changes = collections.OrderedDict()
326 c.range_diff_on = self.request.GET.get('range-diff') == "1"
327 359
328 360 c.at_version = at_version
329 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 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 366 c.at_version_num, versions)
334 367
335 368 (prev_pull_request_latest,
@@ -340,9 +373,9 b' class RepoPullRequestsView(RepoAppView, '
340 373
341 374 c.from_version = prev_at_version
342 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 377 else None)
345 c.from_version_pos = ChangesetComment.get_index_from_version(
378 c.from_version_index = ChangesetComment.get_index_from_version(
346 379 c.from_version_num, versions)
347 380
348 381 # define if we're in COMPARE mode or VIEW at version mode
@@ -355,14 +388,17 b' class RepoPullRequestsView(RepoAppView, '
355 388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
356 389 raise HTTPNotFound()
357 390
358 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
359 pull_request_at_ver)
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
360 392
361 393 c.pull_request = pull_request_display_obj
362 394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
363 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 402 c.allowed_to_change_status = False
367 403 c.allowed_to_update = False
368 404 c.allowed_to_merge = False
@@ -391,12 +427,9 b' class RepoPullRequestsView(RepoAppView, '
391 427 'rules' in pull_request_latest.reviewer_data:
392 428 rules = pull_request_latest.reviewer_data['rules'] or {}
393 429 try:
394 c.forbid_adding_reviewers = rules.get(
395 'forbid_adding_reviewers')
396 c.forbid_author_to_review = rules.get(
397 'forbid_author_to_review')
398 c.forbid_commit_author_to_review = rules.get(
399 'forbid_commit_author_to_review')
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
400 433 except Exception:
401 434 pass
402 435
@@ -421,41 +454,37 b' class RepoPullRequestsView(RepoAppView, '
421 454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
422 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 459 # reviewers and statuses
427 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
428 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
460 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
461 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
429 462
430 # GENERAL COMMENTS with versions #
431 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
432 q = q.order_by(ChangesetComment.comment_id.asc())
433 general_comments = q
463 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
464 member_reviewer = h.reviewer_as_json(
465 member, reasons=reasons, mandatory=mandatory,
466 user_group=review_obj.rule_user_group_data()
467 )
434 468
435 # pick comments we want to render at current version
436 c.comment_versions = comments_model.aggregate_comments(
437 general_comments, versions, c.at_version_num)
438 c.comments = c.comment_versions[c.at_version_num]['until']
469 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
470 member_reviewer['review_status'] = current_review_status
471 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
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 #
441 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
442 q = q.order_by(ChangesetComment.comment_id.asc())
443 inline_comments = q
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
476
477
444 478
445 c.inline_versions = comments_model.aggregate_comments(
446 inline_comments, versions, c.at_version_num, inline=True)
479
480 general_comments, inline_comments = \
481 self.register_comments_vars(c, pull_request_latest, versions)
447 482
448 483 # TODOs
449 484 c.unresolved_comments = CommentsModel() \
450 .get_pull_request_unresolved_todos(pull_request)
485 .get_pull_request_unresolved_todos(pull_request_latest)
451 486 c.resolved_comments = CommentsModel() \
452 .get_pull_request_resolved_todos(pull_request)
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]
487 .get_pull_request_resolved_todos(pull_request_latest)
459 488
460 489 # if we use version, then do not show later comments
461 490 # than current version
@@ -522,8 +551,8 b' class RepoPullRequestsView(RepoAppView, '
522 551
523 552 # empty version means latest, so we keep this to prevent
524 553 # double caching
525 version_normalized = version or 'latest'
526 from_version_normalized = from_version or 'latest'
554 version_normalized = version or PullRequest.LATEST_VER
555 from_version_normalized = from_version or PullRequest.LATEST_VER
527 556
528 557 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
529 558 cache_file_path = diff_cache_exist(
@@ -719,7 +748,7 b' class RepoPullRequestsView(RepoAppView, '
719 748
720 749 # current user review statuses for each version
721 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 752 for co in general_comments:
724 753 if co.author.user_id == self._rhodecode_user.user_id:
725 754 status = co.status_change
@@ -939,6 +968,83 b' class RepoPullRequestsView(RepoAppView, '
939 968 @NotAnonymous()
940 969 @HasRepoPermissionAnyDecorator(
941 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 1048 @CSRFRequired()
943 1049 @view_config(
944 1050 route_name='pullrequest_create', request_method='POST',
@@ -1100,7 +1206,7 b' class RepoPullRequestsView(RepoAppView, '
1100 1206 self.request.matchdict['pull_request_id'])
1101 1207 _ = self.request.translate
1102 1208
1103 self.load_default_context()
1209 c = self.load_default_context()
1104 1210 redirect_url = None
1105 1211
1106 1212 if pull_request.is_closed():
@@ -1111,6 +1217,8 b' class RepoPullRequestsView(RepoAppView, '
1111 1217 'redirect_url': redirect_url}
1112 1218
1113 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 1223 # only owner or admin can update it
1116 1224 allowed_to_update = PullRequestModel().check_user_update(
@@ -1134,7 +1242,7 b' class RepoPullRequestsView(RepoAppView, '
1134 1242 return {'response': True,
1135 1243 'redirect_url': redirect_url}
1136 1244
1137 self._update_commits(pull_request)
1245 self._update_commits(c, pull_request)
1138 1246 if force_refresh:
1139 1247 redirect_url = h.route_path(
1140 1248 'pullrequest_show', repo_name=self.db_repo_name,
@@ -1170,7 +1278,7 b' class RepoPullRequestsView(RepoAppView, '
1170 1278 h.flash(msg, category='success')
1171 1279 return
1172 1280
1173 def _update_commits(self, pull_request):
1281 def _update_commits(self, c, pull_request):
1174 1282 _ = self.request.translate
1175 1283
1176 1284 with pull_request.set_state(PullRequest.STATE_UPDATING):
@@ -1198,13 +1306,18 b' class RepoPullRequestsView(RepoAppView, '
1198 1306 change_source=changed)
1199 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 1309 message = msg + (
1204 1310 ' - <a onclick="window.location.reload()">'
1205 1311 '<strong>{}</strong></a>'.format(_('Reload page')))
1312
1313 message_obj = {
1314 'message': message,
1315 'level': 'success',
1316 'topic': '/notifications'
1317 }
1318
1206 1319 channelstream.post_message(
1207 channel, message, self._rhodecode_user.username,
1320 c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
1208 1321 registry=self.request.registry)
1209 1322 else:
1210 1323 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
@@ -1474,6 +1587,7 b' class RepoPullRequestsView(RepoAppView, '
1474 1587 }
1475 1588 if comment:
1476 1589 c.co = comment
1590 c.at_version_num = None
1477 1591 rendered_comment = render(
1478 1592 'rhodecode:templates/changeset/changeset_comment_block.mako',
1479 1593 self._get_template_context(c), self.request)
@@ -810,8 +810,7 b' import tzlocal'
810 810 local_timezone = tzlocal.get_localzone()
811 811
812 812
813 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
814 title = value or format_date(datetime_iso)
813 def get_timezone(datetime_iso, time_is_local=False):
815 814 tzinfo = '+00:00'
816 815
817 816 # detect if we have a timezone info, otherwise, add it
@@ -822,6 +821,12 b' def age_component(datetime_iso, value=No'
822 821 timezone = force_timezone or local_timezone
823 822 offset = timezone.localize(datetime_iso).strftime('%z')
824 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 831 return literal(
827 832 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
@@ -1650,17 +1655,18 b' def get_active_pattern_entries(repo_name'
1650 1655
1651 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 1662 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1655 1663
1656 allowed_formats = ['html', 'rst', 'markdown',
1657 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1658 if link_format not in allowed_formats:
1664 if link_format not in allowed_link_formats:
1659 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 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 1670 active_entries = get_active_pattern_entries(repo_name)
1665 1671
1666 1672 issues_data = []
@@ -1718,7 +1724,8 b' def process_patterns(text_string, repo_n'
1718 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 1730 Parses given text message and makes proper links.
1724 1731 issues are linked to given issue-server, and rest is a commit link
@@ -1741,6 +1748,9 b' def urlify_commit_message(commit_text, r'
1741 1748 new_text, issues = process_patterns(new_text, repository or '',
1742 1749 active_entries=active_pattern_entries)
1743 1750
1751 if issues_container is not None:
1752 issues_container.extend(issues)
1753
1744 1754 return literal(new_text)
1745 1755
1746 1756
@@ -1781,7 +1791,7 b' def renderer_from_filename(filename, exc'
1781 1791
1782 1792
1783 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 1796 def maybe_convert_relative_links(html_source):
1787 1797 if relative_urls:
@@ -1798,6 +1808,8 b" def render(source, renderer='rst', menti"
1798 1808 source, issues = process_patterns(
1799 1809 source, repo_name, link_format='rst',
1800 1810 active_entries=active_pattern_entries)
1811 if issues_container is not None:
1812 issues_container.extend(issues)
1801 1813
1802 1814 return literal(
1803 1815 '<div class="rst-block">%s</div>' %
@@ -1810,6 +1822,8 b" def render(source, renderer='rst', menti"
1810 1822 source, issues = process_patterns(
1811 1823 source, repo_name, link_format='markdown',
1812 1824 active_entries=active_pattern_entries)
1825 if issues_container is not None:
1826 issues_container.extend(issues)
1813 1827
1814 1828 return literal(
1815 1829 '<div class="markdown-block">%s</div>' %
@@ -91,8 +91,7 b' class CommentsModel(BaseModel):'
91 91 # group by versions, and count until, and display objects
92 92
93 93 comment_groups = collections.defaultdict(list)
94 [comment_groups[
95 _co.pull_request_version_id].append(_co) for _co in comments]
94 [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
96 95
97 96 def yield_comments(pos):
98 97 for co in comment_groups[pos]:
@@ -456,37 +455,53 b' class CommentsModel(BaseModel):'
456 455 else:
457 456 action = 'repo.commit.comment.create'
458 457
458 comment_id = comment.comment_id
459 459 comment_data = comment.get_api_data()
460
460 461 self._log_audit_action(
461 462 action, {'data': comment_data}, auth_user, comment)
462 463
463 msg_url = ''
464 464 channel = None
465 465 if commit_obj:
466 msg_url = commit_comment_url
467 466 repo_name = repo.repo_name
468 467 channel = u'/repo${}$/commit/{}'.format(
469 468 repo_name,
470 469 commit_obj.raw_id
471 470 )
472 471 elif pull_request_obj:
473 msg_url = pr_comment_url
474 472 repo_name = pr_target_repo.repo_name
475 473 channel = u'/repo${}$/pr/{}'.format(
476 474 repo_name,
477 pull_request_id
475 pull_request_obj.pull_request_id
478 476 )
479 477
480 message = '<strong>{}</strong> {} - ' \
481 '<a onclick="window.location=\'{}\';' \
482 'window.location.reload()">' \
483 '<strong>{}</strong></a>'
478 if channel:
479 username = user.username
480 message = '<strong>{}</strong> {} #{}, {}'
484 481 message = message.format(
485 user.username, _('made a comment'), msg_url,
486 _('Show it now'))
482 username,
483 _('posted a new comment'),
484 comment_id,
485 _('Refresh the page to see new comments.'))
486
487 message_obj = {
488 'message': message,
489 'level': 'success',
490 'topic': '/notifications'
491 }
487 492
488 493 channelstream.post_message(
489 channel, message, user.username,
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,
490 505 registry=get_current_registry())
491 506
492 507 return comment
@@ -643,14 +658,14 b' class CommentsModel(BaseModel):'
643 658
644 659 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
645 660 version=None):
646 inline_cnt = 0
661 inline_comms = []
647 662 for fname, per_line_comments in inline_comments.iteritems():
648 663 for lno, comments in per_line_comments.iteritems():
649 664 for comm in comments:
650 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 670 def get_outdated_comments(self, repo_id, pull_request):
656 671 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
@@ -3821,16 +3821,35 b' class ChangesetComment(Base, BaseModel):'
3821 3821 """
3822 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 3839 def older_than_version(self, version):
3827 3840 """
3828 3841 Checks if comment is made from previous version than given
3829 3842 """
3830 3843 if version is None:
3831 return self.pull_request_version_id is not None
3832
3833 return self.pull_request_version_id < version
3844 return self.pull_request_version != version
3845
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 3854 @property
3836 3855 def commit_id(self):
@@ -4327,6 +4346,7 b' class PullRequest(Base, _PullRequestBase'
4327 4346 __table_args__ = (
4328 4347 base_table_args,
4329 4348 )
4349 LATEST_VER = 'latest'
4330 4350
4331 4351 pull_request_id = Column(
4332 4352 'pull_request_id', Integer(), nullable=False, primary_key=True)
@@ -4385,6 +4405,10 b' class PullRequest(Base, _PullRequestBase'
4385 4405 def pull_request_version_id(self):
4386 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 4412 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4389 4413
4390 4414 attrs.author = StrictAttributeDict(
@@ -4449,6 +4473,10 b' class PullRequest(Base, _PullRequestBase'
4449 4473 """
4450 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 4481 class PullRequestVersion(Base, _PullRequestBase):
4454 4482 __tablename__ = 'pull_request_versions'
@@ -240,14 +240,14 b' div.markdown-block ol {'
240 240 div.markdown-block ul.checkbox li,
241 241 div.markdown-block ol.checkbox li {
242 242 list-style: none !important;
243 margin: 6px !important;
243 margin: 0px !important;
244 244 padding: 0 !important;
245 245 }
246 246
247 247 div.markdown-block ul li,
248 248 div.markdown-block ol li {
249 249 list-style: disc !important;
250 margin: 6px !important;
250 margin: 0px !important;
251 251 padding: 0 !important;
252 252 }
253 253
@@ -1510,18 +1510,14 b' table.integrations {'
1510 1510 min-height: 55px;
1511 1511 }
1512 1512
1513 .reviewers_member {
1514 width: 100%;
1515 overflow: auto;
1516 }
1517 1513 .reviewer_reason {
1518 1514 padding-left: 20px;
1519 1515 line-height: 1.5em;
1520 1516 }
1521 1517 .reviewer_status {
1522 1518 display: inline-block;
1523 width: 25px;
1524 min-width: 25px;
1519 width: 20px;
1520 min-width: 20px;
1525 1521 height: 1.2em;
1526 1522 line-height: 1em;
1527 1523 }
@@ -1544,23 +1540,17 b' table.integrations {'
1544 1540 }
1545 1541
1546 1542 .reviewer_member_mandatory {
1547 position: absolute;
1548 left: 15px;
1549 top: 8px;
1550 1543 width: 16px;
1551 1544 font-size: 11px;
1552 1545 margin: 0;
1553 1546 padding: 0;
1554 1547 color: black;
1548 opacity: 0.4;
1555 1549 }
1556 1550
1557 1551 .reviewer_member_mandatory_remove,
1558 1552 .reviewer_member_remove {
1559 position: absolute;
1560 right: 0;
1561 top: 0;
1562 1553 width: 16px;
1563 margin-bottom: 10px;
1564 1554 padding: 0;
1565 1555 color: black;
1566 1556 }
@@ -1617,7 +1607,8 b' table.integrations {'
1617 1607 .td-todo-number {
1618 1608 text-align: left;
1619 1609 white-space: nowrap;
1620 width: 15%;
1610 width: 1%;
1611 padding-right: 2px;
1621 1612 }
1622 1613
1623 1614 .td-todo-gravatar {
@@ -1641,10 +1632,13 b' table.integrations {'
1641 1632 text-overflow: ellipsis;
1642 1633 }
1643 1634
1635 table.group_members {
1636 width: 100%
1637 }
1638
1644 1639 .group_members {
1645 1640 margin-top: 0;
1646 1641 padding: 0;
1647 list-style: outside none none;
1648 1642
1649 1643 img {
1650 1644 height: @gravatar-size;
@@ -246,6 +246,8 b' function registerRCRoutes() {'
246 246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
247 247 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
248 248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
249 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
250 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
249 251 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
250 252 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
251 253 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
@@ -28,9 +28,12 b' export class RhodecodeApp extends Polyme'
28 28 super.connectedCallback();
29 29 ccLog.debug('rhodeCodeApp created');
30 30 $.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
31 $.Topic('/comment').subscribe(this.handleComment.bind(this));
31 32 $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this));
32 33 $.Topic('/connection_controller/subscribe').subscribe(
33 this.subscribeToChannelTopic.bind(this));
34 this.subscribeToChannelTopic.bind(this)
35 );
36
34 37 // this event can be used to coordinate plugins to do their
35 38 // initialization before channelstream is kicked off
36 39 $.Topic('/__MAIN_APP__').publish({});
@@ -71,6 +74,14 b' export class RhodecodeApp extends Polyme'
71 74
72 75 }
73 76
77 handleComment(data) {
78 if (data.message.comment_id) {
79 if (window.refreshAllComments !== undefined) {
80 refreshAllComments()
81 }
82 }
83 }
84
74 85 faviconUpdate(data) {
75 86 this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count;
76 87 }
@@ -95,6 +106,7 b' export class RhodecodeApp extends Polyme'
95 106 }
96 107 // append any additional channels registered in other plugins
97 108 $.Topic('/connection_controller/subscribe').processPrepared();
109
98 110 channelstreamConnection.connect();
99 111 }
100 112 }
@@ -157,8 +169,7 b' export class RhodecodeApp extends Polyme'
157 169
158 170 handleConnected(event) {
159 171 var channelstreamConnection = this.getChannelStreamConnection();
160 channelstreamConnection.set('channelsState',
161 event.detail.channels_info);
172 channelstreamConnection.set('channelsState', event.detail.channels_info);
162 173 channelstreamConnection.set('userState', event.detail.state);
163 174 channelstreamConnection.set('channels', event.detail.channels);
164 175 this.propagageChannelsState();
@@ -677,7 +677,9 b' var feedLifetimeOptions = function(query'
677 677 query.callback(data);
678 678 };
679 679
680
680 /*
681 * Retrievew via templateContext.session_attrs.key
682 * */
681 683 var storeUserSessionAttr = function (key, val) {
682 684
683 685 var postData = {
@@ -670,8 +670,20 b' var CommentsController = function() {'
670 670
671 671 var success = function(response) {
672 672 $comment.remove();
673
674 if (window.updateSticky !== undefined) {
675 // potentially our comments change the active window size, so we
676 // notify sticky elements
677 updateSticky()
678 }
679
680 if (window.refreshAllComments !== undefined) {
681 // if we have this handler, run it, and refresh all comments boxes
682 refreshAllComments()
683 }
673 684 return false;
674 685 };
686
675 687 var failure = function(jqXHR, textStatus, errorThrown) {
676 688 var prefix = "Error while deleting this comment.\n"
677 689 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
@@ -682,6 +694,9 b' var CommentsController = function() {'
682 694 return false;
683 695 };
684 696 ajaxPOST(url, postData, success, failure);
697
698
699
685 700 }
686 701
687 702 this.deleteComment = function(node) {
@@ -727,6 +742,15 b' var CommentsController = function() {'
727 742 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
728 743 $filediff.toggleClass('hide-comments');
729 744 }
745
746 // since we change the height of the diff container that has anchor points for upper
747 // sticky header, we need to tell it to re-calculate those
748 if (window.updateSticky !== undefined) {
749 // potentially our comments change the active window size, so we
750 // notify sticky elements
751 updateSticky()
752 }
753
730 754 return false;
731 755 };
732 756
@@ -747,7 +771,7 b' var CommentsController = function() {'
747 771 var cm = commentForm.getCmInstance();
748 772
749 773 if (resolvesCommentId){
750 var placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
774 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
751 775 }
752 776
753 777 setTimeout(function() {
@@ -1077,9 +1101,15 b' var CommentsController = function() {'
1077 1101 updateSticky()
1078 1102 }
1079 1103
1104 if (window.refreshAllComments !== undefined) {
1105 // if we have this handler, run it, and refresh all comments boxes
1106 refreshAllComments()
1107 }
1108
1080 1109 commentForm.setActionButtonsDisabled(false);
1081 1110
1082 1111 };
1112
1083 1113 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1084 1114 var prefix = "Error while editing comment.\n"
1085 1115 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
@@ -1209,6 +1239,11 b' var CommentsController = function() {'
1209 1239 updateSticky()
1210 1240 }
1211 1241
1242 if (window.refreshAllComments !== undefined) {
1243 // if we have this handler, run it, and refresh all comments boxes
1244 refreshAllComments()
1245 }
1246
1212 1247 commentForm.setActionButtonsDisabled(false);
1213 1248
1214 1249 };
@@ -98,10 +98,13 b' ReviewersController = function () {'
98 98 var self = this;
99 99 this.$reviewRulesContainer = $('#review_rules');
100 100 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$userRule = $('.pr-user-rule-container');
101 102 this.forbidReviewUsers = undefined;
102 103 this.$reviewMembers = $('#review_members');
103 104 this.currentRequest = null;
104 105 this.diffData = null;
106 this.enabledRules = [];
107
105 108 //dummy handler, we might register our own later
106 109 this.diffDataHandler = function(data){};
107 110
@@ -116,14 +119,17 b' ReviewersController = function () {'
116 119
117 120 this.hideReviewRules = function () {
118 121 self.$reviewRulesContainer.hide();
122 $(self.$userRule.selector).hide();
119 123 };
120 124
121 125 this.showReviewRules = function () {
122 126 self.$reviewRulesContainer.show();
127 $(self.$userRule.selector).show();
123 128 };
124 129
125 130 this.addRule = function (ruleText) {
126 131 self.showReviewRules();
132 self.enabledRules.push(ruleText);
127 133 return '<div>- {0}</div>'.format(ruleText)
128 134 };
129 135
@@ -179,6 +185,7 b' ReviewersController = function () {'
179 185 _gettext('Reviewers picked from source code changes.'))
180 186 )
181 187 }
188
182 189 if (data.rules.forbid_adding_reviewers) {
183 190 $('#add_reviewer_input').remove();
184 191 self.$rulesList.append(
@@ -186,6 +193,7 b' ReviewersController = function () {'
186 193 _gettext('Adding new reviewers is forbidden.'))
187 194 )
188 195 }
196
189 197 if (data.rules.forbid_author_to_review) {
190 198 self.forbidReviewUsers.push(data.rules_data.pr_author);
191 199 self.$rulesList.append(
@@ -193,6 +201,7 b' ReviewersController = function () {'
193 201 _gettext('Author is not allowed to be a reviewer.'))
194 202 )
195 203 }
204
196 205 if (data.rules.forbid_commit_author_to_review) {
197 206
198 207 if (data.rules_data.forbidden_users) {
@@ -208,6 +217,12 b' ReviewersController = function () {'
208 217 )
209 218 }
210 219
220 // we don't have any rules set, so we inform users about it
221 if (self.enabledRules.length === 0) {
222 self.addRule(
223 _gettext('No review rules set.'))
224 }
225
211 226 return self.forbidReviewUsers
212 227 };
213 228
@@ -347,11 +362,12 b' ReviewersController = function () {'
347 362 members.innerHTML += renderTemplate('reviewMemberEntry', {
348 363 'member': reviewer_obj,
349 364 'mandatory': mandatory,
365 'reasons': reasons,
350 366 'allowed_to_update': true,
351 367 'review_status': 'not_reviewed',
352 368 'review_status_label': _gettext('Not Reviewed'),
353 'reasons': reasons,
354 'create': true
369 'user_group': reviewer_obj.user_group,
370 'create': true,
355 371 });
356 372 tooltipActivate();
357 373 }
@@ -600,7 +616,7 b' VersionController = function () {'
600 616 var $elem = $(elem);
601 617 var $target = $(target);
602 618
603 if ($target.is(':visible')) {
619 if ($target.is(':visible') || $target.length === 0) {
604 620 $target.hide();
605 621 $elem.html($elem.data('toggleOn'))
606 622 } else {
@@ -10,12 +10,14 b''
10 10
11 11 <%namespace name="base" file="/base/base.mako"/>
12 12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
13
14 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
14 15 <% latest_ver = len(getattr(c, 'versions', [])) %>
16
15 17 % if inline:
16 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
18 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
17 19 % else:
18 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
20 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
19 21 % endif
20 22
21 23 <div class="comment
@@ -153,38 +155,38 b''
153 155 </div>
154 156 %endif
155 157
156 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
158 <a class="permalink" href="#comment-${comment.comment_id}">&para; #${comment.comment_id}</a>
157 159
158 160 <div class="comment-links-block">
159 161
160 162 % if inline:
161 163 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
162 164 % if outdated_at_ver:
163 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
164 outdated ${'v{}'.format(pr_index_ver)} |
165 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
166 outdated ${'v{}'.format(comment_ver)} |
165 167 </code>
166 % elif pr_index_ver:
167 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
168 ${'v{}'.format(pr_index_ver)} |
168 % elif comment_ver:
169 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
170 ${'v{}'.format(comment_ver)} |
169 171 </code>
170 172 % endif
171 173 </a>
172 174 % else:
173 % if pr_index_ver:
175 % if comment_ver:
174 176
175 177 % if comment.outdated:
176 178 <a class="pr-version"
177 179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
178 180 >
179 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
181 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
180 182 </a> |
181 183 % else:
182 184 <a class="tooltip pr-version"
183 title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}"
185 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
184 186 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
185 187 >
186 188 <code class="pr-version-num">
187 ${'v{}'.format(pr_index_ver)}
189 ${'v{}'.format(comment_ver)}
188 190 </code>
189 191 </a> |
190 192 % endif
@@ -21,8 +21,9 b''
21 21 ## to speed up lookups cache some functions before the loop
22 22 <%
23 23 active_patterns = h.get_active_pattern_entries(c.repo_name)
24 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
24 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns, issues_container=getattr(c, 'referenced_commit_issues', None))
25 25 %>
26
26 27 %for commit in c.commit_ranges:
27 28 <tr id="row-${commit.raw_id}"
28 29 commit_id="${commit.raw_id}"
@@ -1,6 +1,10 b''
1 1 <%text>
2 2 <div style="display: none">
3 3
4 <script>
5 var CG = new ColorGenerator();
6 </script>
7
4 8 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
5 9
6 10 <%
@@ -34,10 +38,6 b" var data_hovercard_url = pyroutes.url('h"
34 38
35 39 </script>
36 40
37 <script>
38 var CG = new ColorGenerator();
39 </script>
40
41 41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
42 42 <%
43 43 if (create) {
@@ -47,26 +47,24 b' var CG = new ColorGenerator();'
47 47 }
48 48
49 49 if (member.user_group && member.user_group.vote_rule) {
50 var groupStyle = 'border-right: 2px solid '+CG.asRGB(CG.getColor(member.user_group.vote_rule));
50 var reviewGroup = '<i class="icon-user-group"></i>';
51 var reviewGroupColor = CG.asRGB(CG.getColor(member.user_group.vote_rule));
51 52 } else {
52 var groupStyle = 'border-right: 2px solid transparent';
53 var reviewGroup = null;
54 var reviewGroupColor = 'transparent';
53 55 }
54 56 %>
55 57
56 <li id="reviewer_<%= member.user_id %>" class="reviewer_entry" style="<%= groupStyle%>" tooltip="Review Group">
58 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
57 59
58 <div class="reviewers_member">
60 <td style="width: 20px">
59 61 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
60 62 <i class="icon-circle review-status-<%= review_status %>"></i>
63 </div>
64 </td>
61 65
62 </div>
66 <td>
63 67 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
64 <% if (mandatory) { %>
65 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
66 <i class="icon-lock"></i>
67 </div>
68 <% } %>
69
70 68 <%-
71 69 renderTemplate('gravatarWithUser', {
72 70 'size': 16,
@@ -78,11 +76,43 b' var CG = new ColorGenerator();'
78 76 'gravatar_url': member.gravatar_link
79 77 })
80 78 %>
79 <span class="tooltip presence-state" style="display: none" title="This users is currently at this page">
80 <i class="icon-eye" style="color: #0ac878"></i>
81 </span>
81 82 </div>
83 </td>
84
85 <td style="width: 10px">
86 <% if (reviewGroup !== null) { %>
87 <span class="tooltip" title="Member of review group from rule: `<%= member.user_group.name %>`" style="color: <%= reviewGroupColor %>">
88 <%- reviewGroup %>
89 </span>
90 <% } %>
91 </td>
82 92
93 <% if (mandatory) { %>
94 <td style="text-align: right;width: 10px;">
95 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
96 <i class="icon-lock"></i>
97 </div>
98 </td>
99
100 <% } else { %>
101 <td>
102 <% if (allowed_to_update) { %>
103 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
104 <i class="icon-remove"></i>
105 </div>
106 <% } %>
107 </td>
108 <% } %>
109
110 </tr>
111
112 <tr>
113 <td colspan="4" style="display: none" class="pr-user-rule-container">
83 114 <input type="hidden" name="__start__" value="reviewer:mapping">
84 115
85
86 116 <%if (member.user_group && member.user_group.vote_rule) {%>
87 117 <div class="reviewer_reason">
88 118
@@ -113,24 +143,11 b' var CG = new ColorGenerator();'
113 143 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
114 144
115 145 <input type="hidden" name="__end__" value="reviewer:mapping">
116
117 <% if (mandatory) { %>
118 <div class="reviewer_member_mandatory_remove" style="visibility: <%= edit_visibility %>;">
119 <i class="icon-remove"></i>
120 </div>
121 <% } else { %>
122 <% if (allowed_to_update) { %>
123 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
124 <i class="icon-remove" ></i>
125 </div>
126 <% } %>
127 <% } %>
128 </div>
129 </li>
146 </td>
147 </tr>
130 148
131 149 </script>
132 150
133
134 151 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
135 152
136 153 <%
@@ -158,7 +175,6 b' if (show_disabled) {'
158 175
159 176 </script>
160 177
161
162 178 </div>
163 179
164 180 <script>
@@ -360,13 +360,13 b' text_monospace = "\'Menlo\', \'Liberation M'
360 360
361 361 div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li {
362 362 list-style: none !important;
363 margin: 6px !important;
363 margin: 0px !important;
364 364 padding: 0 !important
365 365 }
366 366
367 367 div.markdown-block ul li, div.markdown-block ol li {
368 368 list-style: disc !important;
369 margin: 6px !important;
369 margin: 0px !important;
370 370 padding: 0 !important
371 371 }
372 372
This diff has been collapsed as it changes many lines, (1139 lines changed) Show them Hide them
@@ -21,7 +21,120 b''
21 21 ${self.repo_menu(active='showpullrequest')}
22 22 </%def>
23 23
24 <%def name="comments_table(comments, counter_num, todo_comments=False)">
25 <%
26 old_comments = False
27 if todo_comments:
28 cls_ = 'todos-content-table'
29 def sorter(entry):
30 user_id = entry.author.user_id
31 resolved = '1' if entry.resolved else '0'
32 if user_id == c.rhodecode_user.user_id:
33 # own comments first
34 user_id = 0
35 return '{}'.format(str(entry.comment_id).zfill(10000))
36 else:
37 cls_ = 'comments-content-table'
38 def sorter(entry):
39 user_id = entry.author.user_id
40 return '{}'.format(str(entry.comment_id).zfill(10000))
41
42
43
44 %>
45 <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}">
46
47 % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))):
48 <%
49 display = ''
50 _cls = ''
51 %>
52 <% comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) %>
53 <%
54 prev_comment_ver_index = 0
55 if loop_obj.previous:
56 prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', []))
57 %>
58 <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %>
59 <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %>
60 <%
61 if (prev_comment_ver_index > comment_ver_index) and old_comments is False:
62 old_comments = True
63 %>
64 % if todo_comments:
65 % if comment_obj.resolved:
66 <% _cls = 'resolved-todo' %>
67 <% display = 'none' %>
68 % endif
69 % else:
70 ## SKIP TODOs we display them in other area
71 % if comment_obj.is_todo:
72 <% display = 'none' %>
73 % endif
74 ## Skip outdated comments
75 % if comment_obj.outdated:
76 <% display = 'none' %>
77 <% _cls = 'hidden-comment' %>
78 % endif
79 % endif
80
81 % if not todo_comments and old_comments:
82 <tr class="old-comments-marker">
83 <td colspan="3"> <code>comments from older versions</code> </td>
84 </tr>
85 ## reset markers so we only show this marker once
86 <% old_comments = None %>
87 % endif
88
89 <tr class="${_cls}" style="display: ${display};">
90 <td class="td-todo-number">
91
92 <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink"
93 href="#comment-${comment_obj.comment_id}"
94 onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})">
95
96 % if todo_comments:
97 % if comment_obj.is_inline:
98 <i class="tooltip icon-code" title="Inline TODO comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
99 % else:
100 <i class="tooltip icon-comment" title="General TODO comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
101 % endif
102 % else:
103 % if comment_obj.outdated:
104 <i class="tooltip icon-comment-toggle" title="Inline Outdated made in v${comment_ver_index}."></i>
105 % elif comment_obj.is_inline:
106 <i class="tooltip icon-code" title="Inline comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
107 % else:
108 <i class="tooltip icon-comment" title="General comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
109 % endif
110 % endif
111
112 #${comment_obj.comment_id}
113 </a>
114 </td>
115
116 <td class="td-todo-gravatar">
117 ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
118 </td>
119 <td class="todo-comment-text-wrapper">
120 <div class="tooltip todo-comment-text timeago" title="${h.format_date(comment_obj.created_on)}" datetime="${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}">
121 <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code>
122 </div>
123 </td>
124 </tr>
125 % endfor
126
127 </table>
128
129 </%def>
130
131
24 132 <%def name="main()">
133 ## Container to gather extracted Tickets
134 <%
135 c.referenced_commit_issues = []
136 c.referenced_desc_issues = []
137 %>
25 138
26 139 <script type="text/javascript">
27 140 // TODO: marcink switch this to pyroutes
@@ -79,7 +192,7 b''
79 192 </div>
80 193
81 194 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
82 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}
195 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
83 196 </div>
84 197
85 198 <div id="pr-desc-edit" class="input textarea" style="display: none;">
@@ -89,29 +202,6 b''
89 202
90 203 <div id="summary" class="fields pr-details-content">
91 204
92 ## review
93 <div class="field">
94 <div class="label-pr-detail">
95 <label>${_('Review status')}:</label>
96 </div>
97 <div class="input">
98 %if c.pull_request_review_status:
99 <div class="tag status-tag-${c.pull_request_review_status}">
100 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
101 <span class="changeset-status-lbl">
102 %if c.pull_request.is_closed():
103 ${_('Closed')},
104 %endif
105
106 ${h.commit_status_lbl(c.pull_request_review_status)}
107
108 </span>
109 </div>
110 - ${_ungettext('calculated based on {} reviewer vote', 'calculated based on {} reviewers votes', len(c.pull_request_reviewers)).format(len(c.pull_request_reviewers))}
111 %endif
112 </div>
113 </div>
114
115 205 ## source
116 206 <div class="field">
117 207 <div class="label-pr-detail">
@@ -231,7 +321,7 b''
231 321 </code>
232 322 </td>
233 323 <td>
234 <input ${('checked="checked"' if c.from_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
324 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
235 325 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
236 326 </td>
237 327 <td>
@@ -281,8 +371,6 b''
281 371 </div>
282 372
283 373
284
285
286 374 </div>
287 375
288 376 </div>
@@ -339,9 +427,9 b''
339 427 <div class="compare_view_commits_title">
340 428 % if not c.compare_mode:
341 429
342 % if c.at_version_pos:
430 % if c.at_version_index:
343 431 <h4>
344 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
432 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
345 433 </h4>
346 434 % endif
347 435
@@ -394,10 +482,11 b''
394 482 </div>
395 483
396 484 % if not c.missing_commits:
485 ## COMPARE RANGE DIFF MODE
397 486 % if c.compare_mode:
398 487 % if c.at_version:
399 488 <h4>
400 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
489 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
401 490 </h4>
402 491
403 492 <div class="subtitle-compare">
@@ -452,7 +541,7 b''
452 541 </td>
453 542 <td class="mid td-description">
454 543 <div class="log-container truncate-wrap">
455 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
544 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
456 545 </div>
457 546 </td>
458 547 </tr>
@@ -463,21 +552,13 b''
463 552
464 553 % endif
465 554
555 ## Regular DIFF
466 556 % else:
467 557 <%include file="/compare/compare_commits.mako" />
468 558 % endif
469 559
470 560 <div class="cs_files">
471 561 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
472 % if c.at_version:
473 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
474 <% c.inline_comments_flat = c.inline_versions[c.at_version_num]['display'] %>
475 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
476 % else:
477 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
478 <% c.inline_comments_flat = c.inline_versions[c.at_version_num]['until'] %>
479 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
480 % endif
481 562
482 563 <%
483 564 pr_menu_data = {
@@ -524,7 +605,7 b''
524 605 ## comments heading with count
525 606 <div class="comments-heading">
526 607 <i class="icon-comment"></i>
527 ${_('Comments')} ${len(c.comments)}
608 ${_('General Comments')} ${len(c.comments)}
528 609 </div>
529 610
530 611 ## render general comments
@@ -561,6 +642,354 b''
561 642 % endif
562 643 </div>
563 644
645
646 ### NAVBOG RIGHT
647 <style>
648
649 .right-sidebar {
650 position: fixed;
651 top: 0px;
652 bottom: 0;
653 right: 0;
654
655 background: #fafafa;
656 z-index: 50;
657 }
658
659 .right-sidebar {
660 border-left: 1px solid #dbdbdb;
661 }
662
663 .right-sidebar.right-sidebar-expanded {
664 width: 320px;
665 overflow: scroll;
666 }
667
668 .right-sidebar.right-sidebar-collapsed {
669 width: 50px;
670 padding: 0;
671 display: block;
672 overflow: hidden;
673 }
674
675 .sidenav {
676 float: right;
677 will-change: min-height;
678 background: #fafafa;
679 width: 100%;
680 padding-top: 50px;
681 }
682
683 .sidebar-toggle {
684 height: 30px;
685 text-align: center;
686 margin: 15px 0px 0 0;
687 }
688 .sidebar-toggle a {
689
690 }
691
692 .sidebar-content {
693 margin-left: 15px;
694 margin-right: 15px;
695 }
696
697 .sidebar-heading {
698 font-size: 1.2em;
699 font-weight: 700;
700 margin-top: 10px;
701 }
702
703 .sidebar-element {
704 margin-top: 20px;
705 }
706 .right-sidebar-collapsed-state {
707 display: flex;
708 flex-direction: column;
709 justify-content: center;
710 align-items: center;
711 padding: 0 10px;
712 cursor: pointer;
713 font-size: 1.3em;
714 margin: 0 -15px;
715 }
716
717 .right-sidebar-collapsed-state:hover {
718 background-color: #dbd9da;
719 }
720
721 .old-comments-marker {
722 text-align: center;
723 }
724
725 .old-comments-marker td {
726 padding-top: 15px;
727 border-bottom: 1px solid #dbd9da;
728 }
729
730 #add_reviewer {
731 padding-top: 10px;
732 }
733
734 </style>
735
736 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
737 <div class="sidenav navbar__inner" >
738 ## TOGGLE
739 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
740 <a href="#toggleSidebar">
741
742 </a>
743 </div>
744
745 ## CONTENT
746 <div class="sidebar-content">
747
748 ## RULES SUMMARY/RULES
749 <div class="sidebar-element clear-both">
750
751 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Reviewers')}">
752 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
753 ${len(c.allowed_reviewers)}
754 </div>
755
756 ## REVIEW RULES
757 <div id="review_rules" style="display: none" class="">
758 <div class="right-sidebar-expanded-state pr-details-title">
759 <span class="sidebar-heading">
760 ${_('Reviewer rules')}
761 </span>
762
763 </div>
764 <div class="pr-reviewer-rules">
765 ## review rules will be appended here, by default reviewers logic
766 </div>
767 <input id="review_data" type="hidden" name="review_data" value="">
768 </div>
769
770 ## REVIEWERS
771 <div class="right-sidebar-expanded-state pr-details-title">
772 <span class="tooltip sidebar-heading" title="${_ungettext('Review status calculated based on {} reviewer vote', 'Review status calculated based on {} reviewers votes', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))}">
773 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
774 ${_('Reviewers')}
775 </span>
776 %if c.allowed_to_update:
777 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
778 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
779 %else:
780 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
781 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
782 %endif
783 </div>
784
785 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
786
787 ## members redering block
788 <input type="hidden" name="__start__" value="review_members:sequence">
789
790 <table id="review_members" class="group_members">
791 ## This content is loaded via JS and ReviewersPanel
792 </table>
793
794 <input type="hidden" name="__end__" value="review_members:sequence">
795 ## end members redering block
796
797 %if not c.pull_request.is_closed():
798 <div id="add_reviewer" class="ac" style="display: none;">
799 %if c.allowed_to_update:
800 % if not c.forbid_adding_reviewers:
801 <div id="add_reviewer_input" class="reviewer_ac">
802 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
803 <div id="reviewers_container"></div>
804 </div>
805 % endif
806 <div class="pull-right">
807 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
808 </div>
809 %endif
810 </div>
811 %endif
812 </div>
813 </div>
814
815 ## ## OBSERVERS
816 ## <div class="sidebar-element clear-both">
817 ## <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
818 ## <i class="icon-eye"></i>
819 ## 0
820 ## </div>
821 ##
822 ## <div class="right-sidebar-expanded-state pr-details-title">
823 ## <span class="sidebar-heading">
824 ## <i class="icon-eye"></i>
825 ## ${_('Observers')}
826 ## </span>
827 ## </div>
828 ## <div class="right-sidebar-expanded-state pr-details-content">
829 ## No observers
830 ## </div>
831 ## </div>
832
833 ## TODOs
834 <div class="sidebar-element clear-both">
835 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
836 <i class="icon-flag-filled"></i>
837 <span id="todos-count">${len(c.unresolved_comments)}</span>
838 </div>
839
840 <div class="right-sidebar-expanded-state pr-details-title">
841 ## Only show unresolved, that is only what matters
842 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
843 <i class="icon-flag-filled"></i>
844 TODOs
845 </span>
846
847 % if not c.at_version:
848 % if c.resolved_comments:
849 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
850 % else:
851 <span class="block-right last-item noselect">Show resolved</span>
852 % endif
853 % endif
854 </div>
855
856 <div class="right-sidebar-expanded-state pr-details-content">
857
858 % if c.at_version:
859 <table>
860 <tr>
861 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
862 </tr>
863 </table>
864 % else:
865 % if c.unresolved_comments + c.resolved_comments:
866 ${comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
867 % else:
868 <table>
869 <tr>
870 <td>
871 ${_('No TODOs yet')}
872 </td>
873 </tr>
874 </table>
875 % endif
876 % endif
877 </div>
878 </div>
879
880 ## COMMENTS
881 <div class="sidebar-element clear-both">
882 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
883 <i class="icon-comment" style="color: #949494"></i>
884 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
885 </div>
886
887 <div class="right-sidebar-expanded-state pr-details-title">
888 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
889 <i class="icon-comment" style="color: #949494"></i>
890 ${_('Comments')}
891
892 ## % if outdated_comm_count_ver:
893 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
894 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
895 ## </a>
896 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
897 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
898
899 ## % else:
900 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
901 ## % endif
902
903 </span>
904
905 % if outdated_comm_count_ver:
906 <span class="block-right action_button last-item noselect" onclick="return versionController.toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
907 % else:
908 <span class="block-right last-item noselect">Show hidden</span>
909 % endif
910
911 </div>
912
913 <div class="right-sidebar-expanded-state pr-details-content">
914 % if c.inline_comments_flat + c.comments:
915 ${comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
916 % else:
917 <table>
918 <tr>
919 <td>
920 ${_('No Comments yet')}
921 </td>
922 </tr>
923 </table>
924 % endif
925 </div>
926
927 </div>
928
929 ## Referenced Tickets
930 <div class="sidebar-element clear-both">
931 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
932 <i class="icon-info-circled"></i>
933 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
934 </div>
935
936 <div class="right-sidebar-expanded-state pr-details-title">
937 <span class="sidebar-heading">
938 <i class="icon-info-circled"></i>
939 ${_('Referenced Tickets')}
940 </span>
941 </div>
942 <div class="right-sidebar-expanded-state pr-details-content">
943 <table>
944
945 <tr><td><code>${_('Pull Request Description')}</code></td></tr>
946 % if c.referenced_desc_issues:
947 % for ticket_dict in c.referenced_desc_issues:
948 <tr>
949 <td>
950 <a href="${ticket_dict.get('url')}">
951 ${ticket_dict.get('id')}
952 </a>
953 </td>
954 </tr>
955 % endfor
956 % else:
957 <tr>
958 <td>
959 ${_('No Ticket data found.')}
960 </td>
961 </tr>
962 % endif
963
964 <tr><td style="padding-top: 10px"><code>${_('Commit Messages')}</code></td></tr>
965 % if c.referenced_commit_issues:
966 % for ticket_dict in c.referenced_commit_issues:
967 <tr>
968 <td>
969 <a href="${ticket_dict.get('url')}">
970 ${ticket_dict.get('id')}
971 </a>
972 </td>
973 </tr>
974 % endfor
975 % else:
976 <tr>
977 <td>
978 ${_('No Ticket data found.')}
979 </td>
980 </tr>
981 % endif
982 </table>
983
984 </div>
985 </div>
986
987 </div>
988
989 </div>
990 </aside>
991
992 ## This JS needs to be at the end
564 993 <script type="text/javascript">
565 994
566 995 versionController = new VersionController();
@@ -571,6 +1000,61 b''
571 1000
572 1001 updateController = new UpdatePrController();
573 1002
1003 /** leak object to top level scope **/
1004 window.PullRequestPresenceController;
1005
1006 (function () {
1007 "use strict";
1008
1009 window.PullRequestPresenceController = function (channel) {
1010 var self = this;
1011 this.channel = channel;
1012 this.users = {};
1013
1014 this.storeUsers = function (users) {
1015 self.users = {}
1016 $.each(users, function(index, value) {
1017 var userId = value.state.id;
1018 self.users[userId] = value.state;
1019 })
1020 }
1021
1022 this.render = function () {
1023 $.each($('.reviewer_entry'), function(index, value) {
1024 var userData = $(value).data();
1025 if(self.users[userData.reviewerUserId] !== undefined){
1026 $(value).find('.presence-state').show();
1027 } else {
1028 $(value).find('.presence-state').hide();
1029 }
1030 })
1031 };
1032
1033 this.handlePresence = function (data) {
1034
1035 if (data.type == 'presence' && data.channel === self.channel) {
1036 this.storeUsers(data.users);
1037 this.render()
1038 }
1039 };
1040
1041 this.handleChannelUpdate = function (data) {
1042
1043 if (data.channel === this.channel) {
1044 this.storeUsers(data.state.users);
1045 this.render()
1046 }
1047
1048 };
1049
1050 /* subscribe our chat to topics that are interesting to it */
1051 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1052 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1053 };
1054
1055 })();
1056
1057
574 1058 $(function () {
575 1059
576 1060 // custom code mirror
@@ -616,6 +1100,8 b''
616 1100 closeButton: $('#close_edit_reviewers'),
617 1101 addButton: $('#add_reviewer'),
618 1102 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
1103 reviewRules: ${c.pull_request_default_reviewers_data_json | n},
1104 setReviewers: ${c.pull_request_set_reviewers_data_json | n},
619 1105
620 1106 init: function () {
621 1107 var self = this;
@@ -624,24 +1110,50 b''
624 1110 });
625 1111 this.closeButton.on('click', function (e) {
626 1112 self.close();
1113 self.renderReviewers();
627 1114 });
1115
1116 self.renderReviewers();
1117
1118 },
1119
1120 renderReviewers: function () {
1121
1122 $('#review_members').html('')
1123 $.each(this.setReviewers.reviewers, function (key, val) {
1124 var member = val;
1125
1126 var entry = renderTemplate('reviewMemberEntry', {
1127 'member': member,
1128 'mandatory': member.mandatory,
1129 'reasons': member.reasons,
1130 'allowed_to_update': member.allowed_to_update,
1131 'review_status': member.review_status,
1132 'review_status_label': member.review_status_label,
1133 'user_group': member.user_group,
1134 'create': false
1135 });
1136
1137 $('#review_members').append(entry)
1138 });
1139 tooltipActivate();
1140
628 1141 },
629 1142
630 1143 edit: function (event) {
631 1144 this.editButton.hide();
632 1145 this.closeButton.show();
633 1146 this.addButton.show();
634 this.removeButtons.css('visibility', 'visible');
1147 $(this.removeButtons.selector).css('visibility', 'visible');
635 1148 // review rules
636 reviewersController.loadReviewRules(
637 ${c.pull_request.reviewer_data_json | n});
1149 reviewersController.loadReviewRules(this.reviewRules);
638 1150 },
639 1151
640 1152 close: function (event) {
641 1153 this.editButton.show();
642 1154 this.closeButton.hide();
643 1155 this.addButton.hide();
644 this.removeButtons.css('visibility', 'hidden');
1156 $(this.removeButtons.selector).css('visibility', 'hidden');
645 1157 // hide review rules
646 1158 reviewersController.hideReviewRules()
647 1159 }
@@ -678,6 +1190,75 b''
678 1190 );
679 1191 };
680 1192
1193 refreshComments = function () {
1194 var params = {
1195 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1196 'repo_name': templateContext.repo_name,
1197 'version': '${request.GET.get('version', '')}',
1198 };
1199 var data = {"comments[]": ["1"]};
1200 var loadUrl = pyroutes.url('pullrequest_comments', params);
1201 var $targetElem = $('.comments-content-table');
1202 $targetElem.css('opacity', 0.3);
1203 $targetElem.load(
1204 loadUrl, data, function (responseText, textStatus, jqXHR) {
1205 if (jqXHR.status !== 200) {
1206 return false;
1207 }
1208 var $counterElem = $('#comments-count');
1209 var newCount = $(responseText).data('counter');
1210 if (newCount !== undefined) {
1211 var callback = function () {
1212 $counterElem.animate({'opacity': 1.00}, 200)
1213 $counterElem.html(newCount);
1214 };
1215 $counterElem.animate({'opacity': 0.15}, 200, callback);
1216 }
1217
1218
1219 $targetElem.css('opacity', 1);
1220 tooltipActivate();
1221 }
1222 );
1223 }
1224
1225 refreshTODOs = function () {
1226 var params = {
1227 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1228 'repo_name': templateContext.repo_name,
1229 'version': '${request.GET.get('version', '')}',
1230 };
1231 var data = {"comments[]": ["1"]};
1232 var loadUrl = pyroutes.url('pullrequest_todos', params);
1233 var $targetElem = $('.todos-content-table');
1234 $targetElem.css('opacity', 0.3);
1235 $targetElem.load(
1236 loadUrl, data, function (responseText, textStatus, jqXHR) {
1237 if (jqXHR.status !== 200) {
1238 return false;
1239 }
1240 var $counterElem = $('#todos-count')
1241 var newCount = $(responseText).data('counter');
1242 if (newCount !== undefined) {
1243 var callback = function () {
1244 $counterElem.animate({'opacity': 1.00}, 200)
1245 $counterElem.html(newCount);
1246 };
1247 $counterElem.animate({'opacity': 0.15}, 200, callback);
1248 }
1249
1250 $targetElem.css('opacity', 1);
1251 tooltipActivate();
1252 }
1253 );
1254
1255 }
1256
1257 refreshAllComments = function() {
1258 refreshComments();
1259 refreshTODOs();
1260 }
1261
681 1262 closePullRequest = function (status) {
682 1263 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
683 1264 return false;
@@ -771,441 +1352,91 b''
771 1352
772 1353 })
773 1354
774 </script>
775
776
777 ### NAVBOG RIGHT
778 <style>
779
780 .right-sidebar {
781 position: fixed;
782 top: 0px;
783 bottom: 0;
784 right: 0;
785
786 background: #fafafa;
787 z-index: 200;
788 }
789
790 .right-sidebar {
791 border-left: 1px solid #dbdbdb;
792 }
793
794 .right-sidebar.right-sidebar-expanded {
795 width: 320px;
796 overflow: scroll;
797 }
798
799 .right-sidebar.right-sidebar-collapsed {
800 width: 62px;
801 padding: 0;
802 display: block;
803 overflow: hidden;
804 }
805
806 .sidenav {
807 float: right;
808 will-change: min-height;
809 background: #fafafa;
810 width: 100%;
811 }
1355 $(document).ready(function () {
812 1356
813 .sidebar-toggle {
814 height: 30px;
815 text-align: center;
816 margin: 15px 0 0 0;
817 }
818 .sidebar-toggle a {
819
820 }
821
822 .sidebar-content {
823 margin-left: 5px;
824 margin-right: 5px;
825 }
826
827 .sidebar-heading {
828 font-size: 1.2em;
829 font-weight: 700;
830 margin-top: 10px;
831 }
1357 var $sideBar = $('.right-sidebar');
1358 var marginExpVal = '320'
1359 var marginColVal = '50'
1360 var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
1361 var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
1362 var marginExpandedHeader = {'margin': '0 -{0}px 0 0'.format(marginExpVal), 'z-index': 10000};
1363 var marginCollapsedHeader = {'margin': '0 -{0}px 0 0'.format(marginColVal), 'z-index': 10000};
832 1364
833 .sidebar-element {
834 margin-top: 20px;
1365 var updateStickyHeader = function() {
1366 if (window.updateSticky !== undefined) {
1367 // potentially our comments change the active window size, so we
1368 // notify sticky elements
1369 updateSticky()
835 1370 }
836 .right-sidebar-collapsed-state {
837 display: flex;
838 flex-direction: column;
839 justify-content: center;
840 align-items: center;
841 padding: 0 10px;
842 cursor: pointer;
843 font-size: 1.3em;
844 margin: 0 -10px;
845 }
846
847 .right-sidebar-collapsed-state:hover {
848 background-color: #dbd9da;
849 }
850
851 .navbar__inner {
852 height: 100%;
853 background: #fafafa;
854 position: relative;
855 1371 }
856 1372
857 </style>
858
859
860
861 <aside class="right-sidebar right-sidebar-expanded">
862 <div class="sidenav">
863 ## TOGGLE
864 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
865 <a href="#toggleSidebar">
866
867 </a>
868 </div>
869
870 ## CONTENT
871 <div class="sidebar-content">
872
873 ## RULES SUMMARY/RULES
874 <div class="sidebar-element clear-both">
875
876 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Reviewers')}">
877 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
878 <br/>${len(c.pull_request_reviewers)}
879 </div>
880
881 ## REVIEW RULES
882 <div id="review_rules" style="display: none" class="">
883 <div class="pr-details-title">
884 <span class="sidebar-heading">
885 ${_('Reviewer rules')}
886 </span>
887
888 </div>
889 <div class="pr-reviewer-rules">
890 ## review rules will be appended here, by default reviewers logic
891 </div>
892 <input id="review_data" type="hidden" name="review_data" value="">
893 </div>
894
895 ## REVIEWERS
896 <div class="right-sidebar-expanded-state pr-details-title">
897 <span class="sidebar-heading">
898 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
899 ${_('Reviewers')} - ${len(c.pull_request_reviewers)}
900 </span>
901 %if c.allowed_to_update:
902 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
903 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
904 %endif
905 </div>
906 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
907
908 ## members redering block
909 <input type="hidden" name="__start__" value="review_members:sequence">
910 <ul id="review_members" class="group_members">
911
912 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
913 <script>
914 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
915 var status = "${(status[0][1].status if status else 'not_reviewed')}";
916 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
917 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
918
919 var entry = renderTemplate('reviewMemberEntry', {
920 'member': member,
921 'mandatory': member.mandatory,
922 'reasons': member.reasons,
923 'allowed_to_update': allowed_to_update,
924 'review_status': status,
925 'review_status_label': status_lbl,
926 'user_group': member.user_group,
927 'create': false
928 });
929 $('#review_members').append(entry)
930 </script>
931
932 % endfor
933
934 </ul>
935
936 <input type="hidden" name="__end__" value="review_members:sequence">
937 ## end members redering block
938
939 %if not c.pull_request.is_closed():
940 <div id="add_reviewer" class="ac" style="display: none;">
941 %if c.allowed_to_update:
942 % if not c.forbid_adding_reviewers:
943 <div id="add_reviewer_input" class="reviewer_ac">
944 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
945 <div id="reviewers_container"></div>
946 </div>
947 % endif
948 <div class="pull-right">
949 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
950 </div>
951 %endif
952 </div>
953 %endif
954 </div>
955 </div>
956
957 ## OBSERVERS
958 <div class="sidebar-element clear-both">
959 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
960 <i class="icon-eye"></i>
961 <br/> 0
962 </div>
963
964 <div class="right-sidebar-expanded-state pr-details-title">
965 <span class="sidebar-heading">
966 <i class="icon-eye"></i>
967 ${_('Observers')}
968 </span>
969 </div>
970 <div class="right-sidebar-expanded-state pr-details-content">
971 No observers - 0
972 </div>
973 </div>
974
975 ## TODOs
976 <div class="sidebar-element clear-both">
977 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
978 <i class="icon-flag-filled"></i>
979 <br/> ${len(c.unresolved_comments)}
980 </div>
981
982 ## TODOs will be listed here
983 <div class="right-sidebar-expanded-state pr-details-title">
984 ## Only show unresolved, that is only what matters
985 <span class="sidebar-heading">
986 <i class="icon-flag-filled"></i>
987 TODOs - ${len(c.unresolved_comments)}
988 ##/ ${(len(c.unresolved_comments) + len(c.resolved_comments))}
989 </span>
990
991 % if not c.at_version:
992 % if c.resolved_comments:
993 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.unresolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
994 % else:
995 <span class="block-right last-item noselect">Show resolved</span>
996 % endif
997 % endif
998 </div>
999
1000 <div class="right-sidebar-expanded-state pr-details-content">
1001
1002 <table class="todo-table">
1003 <%
1004 def sorter(entry):
1005 user_id = entry.author.user_id
1006 resolved = '1' if entry.resolved else '0'
1007 if user_id == c.rhodecode_user.user_id:
1008 # own comments first
1009 user_id = 0
1010 return '{}_{}_{}'.format(resolved, user_id, str(entry.comment_id).zfill(100))
1011 %>
1012
1013 % if c.at_version:
1014 <tr>
1015 <td class="unresolved-todo-text">${_('unresolved TODOs unavailable in this view')}.</td>
1016 </tr>
1017 % else:
1018 % for todo_comment in sorted(c.unresolved_comments + c.resolved_comments, key=sorter):
1019 <% resolved = todo_comment.resolved %>
1020 % if inline:
1021 <% outdated_at_ver = todo_comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
1022 % else:
1023 <% outdated_at_ver = todo_comment.older_than_version(getattr(c, 'at_version_num', None)) %>
1024 % endif
1025
1026 <tr ${('class="unresolved-todo" style="display: none"' if resolved else '') |n}>
1027
1028 <td class="td-todo-number">
1029 % if resolved:
1030 <a class="permalink todo-resolved tooltip" title="${_('Resolved by comment #{}').format(todo_comment.resolved.comment_id)}" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
1031 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
1032 % else:
1033 <a class="permalink" href="#comment-${todo_comment.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${todo_comment.comment_id}'), 0, ${h.json.dumps(outdated_at_ver)})">
1034 <i class="icon-flag-filled"></i> ${todo_comment.comment_id}</a>
1035 % endif
1036 </td>
1037 <td class="td-todo-gravatar">
1038 ${base.gravatar(todo_comment.author.email, 16, user=todo_comment.author, tooltip=True, extra_class=['no-margin'])}
1039 </td>
1040 <td class="todo-comment-text-wrapper">
1041 <div class="todo-comment-text">
1042 <code>${h.chop_at_smart(todo_comment.text, '\n', suffix_if_chopped='...')}</code>
1043 </div>
1044 </td>
1045
1046 </tr>
1047 % endfor
1048
1049 % if len(c.unresolved_comments) == 0:
1050 <tr>
1051 <td class="unresolved-todo-text">${_('No unresolved TODOs')}.</td>
1052 </tr>
1053 % endif
1054
1055 % endif
1056
1057 </table>
1058
1059 </div>
1060 </div>
1061
1062 ## COMMENTS
1063 <div class="sidebar-element clear-both">
1064 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
1065 <i class="icon-comment" style="color: #949494"></i>
1066 <br/> ${len(c.inline_comments_flat+c.comments)}
1067 </div>
1068
1069 <div class="right-sidebar-expanded-state pr-details-title">
1070 <span class="sidebar-heading">
1071 <i class="icon-comment" style="color: #949494"></i>
1072 ${_('Comments')} - ${len(c.inline_comments_flat+c.comments)}
1073 ##${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))} /
1074 ##${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(len(c.inline_comments_flat))}
1075
1076 ## TODO check why this ins't working
1077 % if pull_request_menu:
1078 <%
1079 outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
1080 %>
1081
1082 % if outdated_comm_count_ver:
1083 <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
1084 (${_("{} Outdated").format(outdated_comm_count_ver)})
1085 </a>
1086 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
1087 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
1088 % else:
1089 (${_("{} Outdated").format(outdated_comm_count_ver)})
1090 % endif
1091
1092 % endif
1093 </span>
1094 <span class="block-right action_button last-item noselect" onclick="return versionController.toggleElement(this, '.hidden-comment');" data-toggle-on="Show all" data-toggle-off="Hide all">Show all</span>
1095 </div>
1096
1097 <div class="right-sidebar-expanded-state pr-details-content">
1098 <table class="todo-table">
1099 <%
1100 def sorter(entry):
1101 user_id = entry.author.user_id
1102 return '{}'.format(str(entry.comment_id).zfill(100))
1103 %>
1104
1105 % for comment_obj in reversed(sorted(c.inline_comments_flat + c.comments, key=sorter)):
1106 <%
1107 display = ''
1108 _cls = ''
1109 %>
1110 ## SKIP TODOs we display them above
1111 % if comment_obj.is_todo:
1112 <% display = 'none' %>
1113 % endif
1114
1115 ## Skip outdated comments
1116 % if comment_obj.outdated:
1117 <% display = 'none' %>
1118 <% _cls = 'hidden-comment' %>
1119 % endif
1120
1121 <tr class="${_cls}" style="display: ${display}">
1122 <td class="td-todo-number">
1123 <a class="permalink" href="#comment-${comment_obj.comment_id}" onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${comment_obj.outdated_js})">
1124 % if comment_obj.outdated:
1125 <i class="tooltip icon-comment-toggle" title="Outdated"></i>
1126 % elif comment_obj.is_inline:
1127 <i class="tooltip icon-code" title="Inline"></i>
1128 % else:
1129 <i class="tooltip icon-comment" title="General"></i>
1130 % endif
1131 ${comment_obj.comment_id}
1132 </a>
1133 </td>
1134 <td class="td-todo-gravatar">
1135 ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
1136 </td>
1137 <td class="todo-comment-text-wrapper">
1138 <div class="todo-comment-text">
1139 <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code>
1140 </div>
1141 </td>
1142 </tr>
1143 % endfor
1144
1145 </table>
1146 </div>
1147
1148 </div>
1149 </div>
1150
1151 </div>
1152 </aside>
1153
1154
1155 <script>
1373 var expandSidebar = function() {
1156 1374 var $sideBar = $('.right-sidebar');
1157 var marginExpanded = {'margin': '0 320px 0 0'};
1158 var marginCollapsed = {'margin': '0 50px 0 0'};
1159
1160 if($sideBar.hasClass('right-sidebar-expanded')) {
1161 1375 $('.outerwrapper').css(marginExpanded);
1376 $('.header').css(marginExpandedHeader);
1162 1377 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1163 1378 $('.right-sidebar-collapsed-state').hide();
1164 1379 $('.right-sidebar-expanded-state').show();
1165 updateSticky()
1166 1380
1167 } else {
1168 $('.outerwrapper').css(marginCollapsed);
1169 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
1170 $('.right-sidebar-collapsed-state').hide();
1171 $('.right-sidebar-expanded-state').show();
1172 updateSticky()
1381 $sideBar.addClass('right-sidebar-expanded')
1382 $sideBar.removeClass('right-sidebar-collapsed')
1173 1383 }
1174 1384
1175 var toggleSidebar = function(){
1385 var collapseSidebar = function() {
1176 1386 var $sideBar = $('.right-sidebar');
1177
1178 if($sideBar.hasClass('right-sidebar-expanded')) {
1179 // collapse now
1180 $sideBar.removeClass('right-sidebar-expanded')
1181 $sideBar.addClass('right-sidebar-collapsed')
1182 1387 $('.outerwrapper').css(marginCollapsed);
1388 $('.header').css(marginCollapsedHeader);
1183 1389 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
1184 1390 $('.right-sidebar-collapsed-state').show();
1185 1391 $('.right-sidebar-expanded-state').hide();
1186 1392
1393 $sideBar.removeClass('right-sidebar-expanded')
1394 $sideBar.addClass('right-sidebar-collapsed')
1395 }
1396
1397 toggleSidebar = function () {
1398 var $sideBar = $('.right-sidebar');
1399
1400 if ($sideBar.hasClass('right-sidebar-expanded')) {
1401 // expanded -> collapsed transition
1402 collapseSidebar();
1403 var sidebarState = 'collapsed';
1404
1187 1405 } else {
1188 // expand now
1189 $('.outerwrapper').css(marginExpanded);
1190 $sideBar.addClass('right-sidebar-expanded')
1191 $sideBar.removeClass('right-sidebar-collapsed')
1192 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1193 $('.right-sidebar-collapsed-state').hide();
1194 $('.right-sidebar-expanded-state').show();
1406 // collapsed -> expanded
1407 expandSidebar();
1408 var sidebarState = 'expanded';
1195 1409 }
1196 1410
1197 1411 // update our other sticky header in same context
1198 updateSticky()
1412 updateStickyHeader();
1413 storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
1414 }
1415
1416 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1417
1418 if (templateContext.session_attrs.sidebarState === 'expanded') {
1419 expanded = true
1420 } else if (templateContext.session_attrs.sidebarState === 'collapsed') {
1421 expanded = false
1199 1422 }
1200 1423
1201 var sidebarElement = document.getElementById('pr-nav-sticky');
1424 // show sidebar since it's hidden on load
1425 $('.right-sidebar').show();
1426
1427 // init based on set initial class, or if defined user session attrs
1428 if (expanded) {
1429 expandSidebar();
1430 updateStickyHeader();
1202 1431
1203 ## sidebar = new StickySidebar(sidebarElement, {
1204 ## containerSelector: '.main',
1205 ## minWidth: 62,
1206 ## innerWrapperSelector: '.navbar__inner',
1207 ## stickyClass: 'is-sticky',
1208 ## });
1432 } else {
1433 collapseSidebar();
1434 updateStickyHeader();
1435 }
1436 var channel = '${c.pr_broadcast_channel}';
1437 new PullRequestPresenceController(channel)
1209 1438
1439 })
1210 1440 </script>
1441
1211 1442 </%def>
General Comments 0
You need to be logged in to leave comments. Login now