##// 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 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b" 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 b" 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 b' 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 }
1523 }
@@ -1544,23 +1540,17 b' table.integrations {'
1544 }
1540 }
1545
1541
1546 .reviewer_member_mandatory {
1542 .reviewer_member_mandatory {
1547 position: absolute;
1548 left: 15px;
1549 top: 8px;
1550 width: 16px;
1543 width: 16px;
1551 font-size: 11px;
1544 font-size: 11px;
1552 margin: 0;
1545 margin: 0;
1553 padding: 0;
1546 padding: 0;
1554 color: black;
1547 color: black;
1548 opacity: 0.4;
1555 }
1549 }
1556
1550
1557 .reviewer_member_mandatory_remove,
1551 .reviewer_member_mandatory_remove,
1558 .reviewer_member_remove {
1552 .reviewer_member_remove {
1559 position: absolute;
1560 right: 0;
1561 top: 0;
1562 width: 16px;
1553 width: 16px;
1563 margin-bottom: 10px;
1564 padding: 0;
1554 padding: 0;
1565 color: black;
1555 color: black;
1566 }
1556 }
@@ -1617,7 +1607,8 b' table.integrations {'
1617 .td-todo-number {
1607 .td-todo-number {
1618 text-align: left;
1608 text-align: left;
1619 white-space: nowrap;
1609 white-space: nowrap;
1620 width: 15%;
1610 width: 1%;
1611 padding-right: 2px;
1621 }
1612 }
1622
1613
1623 .td-todo-gravatar {
1614 .td-todo-gravatar {
@@ -1641,10 +1632,13 b' table.integrations {'
1641 text-overflow: ellipsis;
1632 text-overflow: ellipsis;
1642 }
1633 }
1643
1634
1635 table.group_members {
1636 width: 100%
1637 }
1638
1644 .group_members {
1639 .group_members {
1645 margin-top: 0;
1640 margin-top: 0;
1646 padding: 0;
1641 padding: 0;
1647 list-style: outside none none;
1648
1642
1649 img {
1643 img {
1650 height: @gravatar-size;
1644 height: @gravatar-size;
@@ -246,6 +246,8 b' function registerRCRoutes() {'
246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
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']);
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 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']);
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 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
251 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
250 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
252 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
251 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
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 super.connectedCallback();
28 super.connectedCallback();
29 ccLog.debug('rhodeCodeApp created');
29 ccLog.debug('rhodeCodeApp created');
30 $.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
30 $.Topic('/notifications').subscribe(this.handleNotifications.bind(this));
31 $.Topic('/comment').subscribe(this.handleComment.bind(this));
31 $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this));
32 $.Topic('/favicon/update').subscribe(this.faviconUpdate.bind(this));
32 $.Topic('/connection_controller/subscribe').subscribe(
33 $.Topic('/connection_controller/subscribe').subscribe(
33 this.subscribeToChannelTopic.bind(this));
34 this.subscribeToChannelTopic.bind(this)
35 );
36
34 // this event can be used to coordinate plugins to do their
37 // this event can be used to coordinate plugins to do their
35 // initialization before channelstream is kicked off
38 // initialization before channelstream is kicked off
36 $.Topic('/__MAIN_APP__').publish({});
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 faviconUpdate(data) {
85 faviconUpdate(data) {
75 this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count;
86 this.shadowRoot.querySelector('rhodecode-favicon').counter = data.count;
76 }
87 }
@@ -95,6 +106,7 b' export class RhodecodeApp extends Polyme'
95 }
106 }
96 // append any additional channels registered in other plugins
107 // append any additional channels registered in other plugins
97 $.Topic('/connection_controller/subscribe').processPrepared();
108 $.Topic('/connection_controller/subscribe').processPrepared();
109
98 channelstreamConnection.connect();
110 channelstreamConnection.connect();
99 }
111 }
100 }
112 }
@@ -157,8 +169,7 b' export class RhodecodeApp extends Polyme'
157
169
158 handleConnected(event) {
170 handleConnected(event) {
159 var channelstreamConnection = this.getChannelStreamConnection();
171 var channelstreamConnection = this.getChannelStreamConnection();
160 channelstreamConnection.set('channelsState',
172 channelstreamConnection.set('channelsState', event.detail.channels_info);
161 event.detail.channels_info);
162 channelstreamConnection.set('userState', event.detail.state);
173 channelstreamConnection.set('userState', event.detail.state);
163 channelstreamConnection.set('channels', event.detail.channels);
174 channelstreamConnection.set('channels', event.detail.channels);
164 this.propagageChannelsState();
175 this.propagageChannelsState();
@@ -677,7 +677,9 b' var feedLifetimeOptions = function(query'
677 query.callback(data);
677 query.callback(data);
678 };
678 };
679
679
680
680 /*
681 * Retrievew via templateContext.session_attrs.key
682 * */
681 var storeUserSessionAttr = function (key, val) {
683 var storeUserSessionAttr = function (key, val) {
682
684
683 var postData = {
685 var postData = {
@@ -558,7 +558,7 b' var CommentsController = function() {'
558 return false;
558 return false;
559 };
559 };
560
560
561 this.showVersion = function (comment_id, comment_history_id) {
561 this.showVersion = function (comment_id, comment_history_id) {
562
562
563 var historyViewUrl = pyroutes.url(
563 var historyViewUrl = pyroutes.url(
564 'repo_commit_comment_history_view',
564 'repo_commit_comment_history_view',
@@ -585,7 +585,7 b' var CommentsController = function() {'
585 successRenderCommit,
585 successRenderCommit,
586 failRenderCommit
586 failRenderCommit
587 );
587 );
588 };
588 };
589
589
590 this.getLineNumber = function(node) {
590 this.getLineNumber = function(node) {
591 var $node = $(node);
591 var $node = $(node);
@@ -670,8 +670,20 b' var CommentsController = function() {'
670
670
671 var success = function(response) {
671 var success = function(response) {
672 $comment.remove();
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 return false;
684 return false;
674 };
685 };
686
675 var failure = function(jqXHR, textStatus, errorThrown) {
687 var failure = function(jqXHR, textStatus, errorThrown) {
676 var prefix = "Error while deleting this comment.\n"
688 var prefix = "Error while deleting this comment.\n"
677 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
689 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
@@ -682,6 +694,9 b' var CommentsController = function() {'
682 return false;
694 return false;
683 };
695 };
684 ajaxPOST(url, postData, success, failure);
696 ajaxPOST(url, postData, success, failure);
697
698
699
685 }
700 }
686
701
687 this.deleteComment = function(node) {
702 this.deleteComment = function(node) {
@@ -727,6 +742,15 b' var CommentsController = function() {'
727 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
742 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
728 $filediff.toggleClass('hide-comments');
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 return false;
754 return false;
731 };
755 };
732
756
@@ -747,7 +771,7 b' var CommentsController = function() {'
747 var cm = commentForm.getCmInstance();
771 var cm = commentForm.getCmInstance();
748
772
749 if (resolvesCommentId){
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 setTimeout(function() {
777 setTimeout(function() {
@@ -1077,9 +1101,15 b' var CommentsController = function() {'
1077 updateSticky()
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 commentForm.setActionButtonsDisabled(false);
1109 commentForm.setActionButtonsDisabled(false);
1081
1110
1082 };
1111 };
1112
1083 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1113 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1084 var prefix = "Error while editing comment.\n"
1114 var prefix = "Error while editing comment.\n"
1085 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1115 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
@@ -1209,6 +1239,11 b' var CommentsController = function() {'
1209 updateSticky()
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 commentForm.setActionButtonsDisabled(false);
1247 commentForm.setActionButtonsDisabled(false);
1213
1248
1214 };
1249 };
@@ -98,10 +98,13 b' ReviewersController = function () {'
98 var self = this;
98 var self = this;
99 this.$reviewRulesContainer = $('#review_rules');
99 this.$reviewRulesContainer = $('#review_rules');
100 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
100 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$userRule = $('.pr-user-rule-container');
101 this.forbidReviewUsers = undefined;
102 this.forbidReviewUsers = undefined;
102 this.$reviewMembers = $('#review_members');
103 this.$reviewMembers = $('#review_members');
103 this.currentRequest = null;
104 this.currentRequest = null;
104 this.diffData = null;
105 this.diffData = null;
106 this.enabledRules = [];
107
105 //dummy handler, we might register our own later
108 //dummy handler, we might register our own later
106 this.diffDataHandler = function(data){};
109 this.diffDataHandler = function(data){};
107
110
@@ -116,14 +119,17 b' ReviewersController = function () {'
116
119
117 this.hideReviewRules = function () {
120 this.hideReviewRules = function () {
118 self.$reviewRulesContainer.hide();
121 self.$reviewRulesContainer.hide();
122 $(self.$userRule.selector).hide();
119 };
123 };
120
124
121 this.showReviewRules = function () {
125 this.showReviewRules = function () {
122 self.$reviewRulesContainer.show();
126 self.$reviewRulesContainer.show();
127 $(self.$userRule.selector).show();
123 };
128 };
124
129
125 this.addRule = function (ruleText) {
130 this.addRule = function (ruleText) {
126 self.showReviewRules();
131 self.showReviewRules();
132 self.enabledRules.push(ruleText);
127 return '<div>- {0}</div>'.format(ruleText)
133 return '<div>- {0}</div>'.format(ruleText)
128 };
134 };
129
135
@@ -179,6 +185,7 b' ReviewersController = function () {'
179 _gettext('Reviewers picked from source code changes.'))
185 _gettext('Reviewers picked from source code changes.'))
180 )
186 )
181 }
187 }
188
182 if (data.rules.forbid_adding_reviewers) {
189 if (data.rules.forbid_adding_reviewers) {
183 $('#add_reviewer_input').remove();
190 $('#add_reviewer_input').remove();
184 self.$rulesList.append(
191 self.$rulesList.append(
@@ -186,6 +193,7 b' ReviewersController = function () {'
186 _gettext('Adding new reviewers is forbidden.'))
193 _gettext('Adding new reviewers is forbidden.'))
187 )
194 )
188 }
195 }
196
189 if (data.rules.forbid_author_to_review) {
197 if (data.rules.forbid_author_to_review) {
190 self.forbidReviewUsers.push(data.rules_data.pr_author);
198 self.forbidReviewUsers.push(data.rules_data.pr_author);
191 self.$rulesList.append(
199 self.$rulesList.append(
@@ -193,6 +201,7 b' ReviewersController = function () {'
193 _gettext('Author is not allowed to be a reviewer.'))
201 _gettext('Author is not allowed to be a reviewer.'))
194 )
202 )
195 }
203 }
204
196 if (data.rules.forbid_commit_author_to_review) {
205 if (data.rules.forbid_commit_author_to_review) {
197
206
198 if (data.rules_data.forbidden_users) {
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 return self.forbidReviewUsers
226 return self.forbidReviewUsers
212 };
227 };
213
228
@@ -347,11 +362,12 b' ReviewersController = function () {'
347 members.innerHTML += renderTemplate('reviewMemberEntry', {
362 members.innerHTML += renderTemplate('reviewMemberEntry', {
348 'member': reviewer_obj,
363 'member': reviewer_obj,
349 'mandatory': mandatory,
364 'mandatory': mandatory,
365 'reasons': reasons,
350 'allowed_to_update': true,
366 'allowed_to_update': true,
351 'review_status': 'not_reviewed',
367 'review_status': 'not_reviewed',
352 'review_status_label': _gettext('Not Reviewed'),
368 'review_status_label': _gettext('Not Reviewed'),
353 'reasons': reasons,
369 'user_group': reviewer_obj.user_group,
354 'create': true
370 'create': true,
355 });
371 });
356 tooltipActivate();
372 tooltipActivate();
357 }
373 }
@@ -600,7 +616,7 b' VersionController = function () {'
600 var $elem = $(elem);
616 var $elem = $(elem);
601 var $target = $(target);
617 var $target = $(target);
602
618
603 if ($target.is(':visible')) {
619 if ($target.is(':visible') || $target.length === 0) {
604 $target.hide();
620 $target.hide();
605 $elem.html($elem.data('toggleOn'))
621 $elem.html($elem.data('toggleOn'))
606 } else {
622 } else {
@@ -10,12 +10,14 b''
10
10
11 <%namespace name="base" file="/base/base.mako"/>
11 <%namespace name="base" file="/base/base.mako"/>
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
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 <% latest_ver = len(getattr(c, 'versions', [])) %>
15 <% latest_ver = len(getattr(c, 'versions', [])) %>
16
15 % if inline:
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 % else:
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 % endif
21 % endif
20
22
21 <div class="comment
23 <div class="comment
@@ -153,38 +155,38 b''
153 </div>
155 </div>
154 %endif
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 <div class="comment-links-block">
160 <div class="comment-links-block">
159
161
160 % if inline:
162 % if inline:
161 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
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 % if outdated_at_ver:
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)}">
165 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
164 outdated ${'v{}'.format(pr_index_ver)} |
166 outdated ${'v{}'.format(comment_ver)} |
165 </code>
167 </code>
166 % elif pr_index_ver:
168 % elif comment_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)}">
169 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
168 ${'v{}'.format(pr_index_ver)} |
170 ${'v{}'.format(comment_ver)} |
169 </code>
171 </code>
170 % endif
172 % endif
171 </a>
173 </a>
172 % else:
174 % else:
173 % if pr_index_ver:
175 % if comment_ver:
174
176
175 % if comment.outdated:
177 % if comment.outdated:
176 <a class="pr-version"
178 <a class="pr-version"
177 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
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 </a> |
182 </a> |
181 % else:
183 % else:
182 <a class="tooltip pr-version"
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 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)}"
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 <code class="pr-version-num">
188 <code class="pr-version-num">
187 ${'v{}'.format(pr_index_ver)}
189 ${'v{}'.format(comment_ver)}
188 </code>
190 </code>
189 </a> |
191 </a> |
190 % endif
192 % endif
@@ -21,8 +21,9 b''
21 ## to speed up lookups cache some functions before the loop
21 ## to speed up lookups cache some functions before the loop
22 <%
22 <%
23 active_patterns = h.get_active_pattern_entries(c.repo_name)
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 %for commit in c.commit_ranges:
27 %for commit in c.commit_ranges:
27 <tr id="row-${commit.raw_id}"
28 <tr id="row-${commit.raw_id}"
28 commit_id="${commit.raw_id}"
29 commit_id="${commit.raw_id}"
@@ -1,6 +1,10 b''
1 <%text>
1 <%text>
2 <div style="display: none">
2 <div style="display: none">
3
3
4 <script>
5 var CG = new ColorGenerator();
6 </script>
7
4 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
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 </script>
39 </script>
36
40
37 <script>
38 var CG = new ColorGenerator();
39 </script>
40
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
41 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
42 <%
42 <%
43 if (create) {
43 if (create) {
@@ -47,26 +47,24 b' var CG = new ColorGenerator();'
47 }
47 }
48
48
49 if (member.user_group && member.user_group.vote_rule) {
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 } else {
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 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
61 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
60 <i class="icon-circle review-status-<%= review_status %>"></i>
62 <i class="icon-circle review-status-<%= review_status %>"></i>
63 </div>
64 </td>
61
65
62 </div>
66 <td>
63 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
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 renderTemplate('gravatarWithUser', {
69 renderTemplate('gravatarWithUser', {
72 'size': 16,
70 'size': 16,
@@ -78,12 +76,44 b' var CG = new ColorGenerator();'
78 'gravatar_url': member.gravatar_link
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 </div>
82 </div>
83 </td>
82
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>
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 <input type="hidden" name="__start__" value="reviewer:mapping">
114 <input type="hidden" name="__start__" value="reviewer:mapping">
84
115
85
116 <%if (member.user_group && member.user_group.vote_rule) { %>
86 <%if (member.user_group && member.user_group.vote_rule) {%>
87 <div class="reviewer_reason">
117 <div class="reviewer_reason">
88
118
89 <%if (member.user_group.vote_rule == -1) {%>
119 <%if (member.user_group.vote_rule == -1) {%>
@@ -92,7 +122,7 b' var CG = new ColorGenerator();'
92 - group votes required: <%= member.user_group.vote_rule %>
122 - group votes required: <%= member.user_group.vote_rule %>
93 <%}%>
123 <%}%>
94 </div>
124 </div>
95 <%}%>
125 <%} %>
96
126
97 <input type="hidden" name="__start__" value="reasons:sequence">
127 <input type="hidden" name="__start__" value="reasons:sequence">
98 <% for (var i = 0; i < reasons.length; i++) { %>
128 <% for (var i = 0; i < reasons.length; i++) { %>
@@ -100,37 +130,24 b' var CG = new ColorGenerator();'
100 <div class="reviewer_reason">- <%= reason %></div>
130 <div class="reviewer_reason">- <%= reason %></div>
101 <input type="hidden" name="reason" value="<%= reason %>">
131 <input type="hidden" name="reason" value="<%= reason %>">
102 <% } %>
132 <% } %>
103 <input type="hidden" name="__end__" value="reasons:sequence">
133 <input type="hidden" name="__end__" value="reasons:sequence">
104
134
105 <input type="hidden" name="__start__" value="rules:sequence">
135 <input type="hidden" name="__start__" value="rules:sequence">
106 <% for (var i = 0; i < member.rules.length; i++) { %>
136 <% for (var i = 0; i < member.rules.length; i++) { %>
107 <% var rule = member.rules[i] %>
137 <% var rule = member.rules[i] %>
108 <input type="hidden" name="rule_id" value="<%= rule %>">
138 <input type="hidden" name="rule_id" value="<%= rule %>">
109 <% } %>
139 <% } %>
110 <input type="hidden" name="__end__" value="rules:sequence">
140 <input type="hidden" name="__end__" value="rules:sequence">
111
141
112 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
142 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
113 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
143 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
114
144
115 <input type="hidden" name="__end__" value="reviewer:mapping">
145 <input type="hidden" name="__end__" value="reviewer:mapping">
116
146 </td>
117 <% if (mandatory) { %>
147 </tr>
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>
130
148
131 </script>
149 </script>
132
150
133
134 <script id="ejs_commentVersion" type="text/template" class="ejsTemplate">
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 </script>
176 </script>
160
177
161
162 </div>
178 </div>
163
179
164 <script>
180 <script>
@@ -360,13 +360,13 b' text_monospace = "\'Menlo\', \'Liberation M'
360
360
361 div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li {
361 div.markdown-block ul.checkbox li, div.markdown-block ol.checkbox li {
362 list-style: none !important;
362 list-style: none !important;
363 margin: 6px !important;
363 margin: 0px !important;
364 padding: 0 !important
364 padding: 0 !important
365 }
365 }
366
366
367 div.markdown-block ul li, div.markdown-block ol li {
367 div.markdown-block ul li, div.markdown-block ol li {
368 list-style: disc !important;
368 list-style: disc !important;
369 margin: 6px !important;
369 margin: 0px !important;
370 padding: 0 !important
370 padding: 0 !important
371 }
371 }
372
372
This diff has been collapsed as it changes many lines, (1173 lines changed) Show them Hide them
@@ -21,7 +21,120 b''
21 ${self.repo_menu(active='showpullrequest')}
21 ${self.repo_menu(active='showpullrequest')}
22 </%def>
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 <%def name="main()">
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 <script type="text/javascript">
139 <script type="text/javascript">
27 // TODO: marcink switch this to pyroutes
140 // TODO: marcink switch this to pyroutes
@@ -79,7 +192,7 b''
79 </div>
192 </div>
80
193
81 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
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 </div>
196 </div>
84
197
85 <div id="pr-desc-edit" class="input textarea" style="display: none;">
198 <div id="pr-desc-edit" class="input textarea" style="display: none;">
@@ -89,29 +202,6 b''
89
202
90 <div id="summary" class="fields pr-details-content">
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 ## source
205 ## source
116 <div class="field">
206 <div class="field">
117 <div class="label-pr-detail">
207 <div class="label-pr-detail">
@@ -231,7 +321,7 b''
231 </code>
321 </code>
232 </td>
322 </td>
233 <td>
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 <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}"/>
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 </td>
326 </td>
237 <td>
327 <td>
@@ -281,8 +371,6 b''
281 </div>
371 </div>
282
372
283
373
284
285
286 </div>
374 </div>
287
375
288 </div>
376 </div>
@@ -339,9 +427,9 b''
339 <div class="compare_view_commits_title">
427 <div class="compare_view_commits_title">
340 % if not c.compare_mode:
428 % if not c.compare_mode:
341
429
342 % if c.at_version_pos:
430 % if c.at_version_index:
343 <h4>
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 </h4>
433 </h4>
346 % endif
434 % endif
347
435
@@ -394,10 +482,11 b''
394 </div>
482 </div>
395
483
396 % if not c.missing_commits:
484 % if not c.missing_commits:
485 ## COMPARE RANGE DIFF MODE
397 % if c.compare_mode:
486 % if c.compare_mode:
398 % if c.at_version:
487 % if c.at_version:
399 <h4>
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 </h4>
490 </h4>
402
491
403 <div class="subtitle-compare">
492 <div class="subtitle-compare">
@@ -452,7 +541,7 b''
452 </td>
541 </td>
453 <td class="mid td-description">
542 <td class="mid td-description">
454 <div class="log-container truncate-wrap">
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 </div>
545 </div>
457 </td>
546 </td>
458 </tr>
547 </tr>
@@ -463,21 +552,13 b''
463
552
464 % endif
553 % endif
465
554
555 ## Regular DIFF
466 % else:
556 % else:
467 <%include file="/compare/compare_commits.mako" />
557 <%include file="/compare/compare_commits.mako" />
468 % endif
558 % endif
469
559
470 <div class="cs_files">
560 <div class="cs_files">
471 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
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 pr_menu_data = {
564 pr_menu_data = {
@@ -524,7 +605,7 b''
524 ## comments heading with count
605 ## comments heading with count
525 <div class="comments-heading">
606 <div class="comments-heading">
526 <i class="icon-comment"></i>
607 <i class="icon-comment"></i>
527 ${_('Comments')} ${len(c.comments)}
608 ${_('General Comments')} ${len(c.comments)}
528 </div>
609 </div>
529
610
530 ## render general comments
611 ## render general comments
@@ -561,6 +642,354 b''
561 % endif
642 % endif
562 </div>
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 <script type="text/javascript">
993 <script type="text/javascript">
565
994
566 versionController = new VersionController();
995 versionController = new VersionController();
@@ -571,6 +1000,61 b''
571
1000
572 updateController = new UpdatePrController();
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 $(function () {
1058 $(function () {
575
1059
576 // custom code mirror
1060 // custom code mirror
@@ -616,6 +1100,8 b''
616 closeButton: $('#close_edit_reviewers'),
1100 closeButton: $('#close_edit_reviewers'),
617 addButton: $('#add_reviewer'),
1101 addButton: $('#add_reviewer'),
618 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
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 init: function () {
1106 init: function () {
621 var self = this;
1107 var self = this;
@@ -624,24 +1110,50 b''
624 });
1110 });
625 this.closeButton.on('click', function (e) {
1111 this.closeButton.on('click', function (e) {
626 self.close();
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 edit: function (event) {
1143 edit: function (event) {
631 this.editButton.hide();
1144 this.editButton.hide();
632 this.closeButton.show();
1145 this.closeButton.show();
633 this.addButton.show();
1146 this.addButton.show();
634 this.removeButtons.css('visibility', 'visible');
1147 $(this.removeButtons.selector).css('visibility', 'visible');
635 // review rules
1148 // review rules
636 reviewersController.loadReviewRules(
1149 reviewersController.loadReviewRules(this.reviewRules);
637 ${c.pull_request.reviewer_data_json | n});
638 },
1150 },
639
1151
640 close: function (event) {
1152 close: function (event) {
641 this.editButton.show();
1153 this.editButton.show();
642 this.closeButton.hide();
1154 this.closeButton.hide();
643 this.addButton.hide();
1155 this.addButton.hide();
644 this.removeButtons.css('visibility', 'hidden');
1156 $(this.removeButtons.selector).css('visibility', 'hidden');
645 // hide review rules
1157 // hide review rules
646 reviewersController.hideReviewRules()
1158 reviewersController.hideReviewRules()
647 }
1159 }
@@ -670,14 +1182,83 b''
670 $('.action-buttons-extra').css('opacity', 0.3);
1182 $('.action-buttons-extra').css('opacity', 0.3);
671
1183
672 $('.pull-request-merge').load(
1184 $('.pull-request-merge').load(
673 loadUrl, function () {
1185 loadUrl, function () {
674 $('.pull-request-merge').css('opacity', 1);
1186 $('.pull-request-merge').css('opacity', 1);
675
1187
676 $('.action-buttons-extra').css('opacity', 1);
1188 $('.action-buttons-extra').css('opacity', 1);
677 }
1189 }
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 closePullRequest = function (status) {
1262 closePullRequest = function (status) {
682 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
1263 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
683 return false;
1264 return false;
@@ -771,441 +1352,91 b''
771
1352
772 })
1353 })
773
1354
774 </script>
1355 $(document).ready(function () {
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 }
812
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 }
832
833 .sidebar-element {
834 margin-top: 20px;
835 }
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 }
856
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
1356
873 ## RULES SUMMARY/RULES
1357 var $sideBar = $('.right-sidebar');
874 <div class="sidebar-element clear-both">
1358 var marginExpVal = '320'
875
1359 var marginColVal = '50'
876 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Reviewers')}">
1360 var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
877 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
1361 var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
878 <br/>${len(c.pull_request_reviewers)}
1362 var marginExpandedHeader = {'margin': '0 -{0}px 0 0'.format(marginExpVal), 'z-index': 10000};
879 </div>
1363 var marginCollapsedHeader = {'margin': '0 -{0}px 0 0'.format(marginColVal), 'z-index': 10000};
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
1364
975 ## TODOs
1365 var updateStickyHeader = function() {
976 <div class="sidebar-element clear-both">
1366 if (window.updateSticky !== undefined) {
977 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
1367 // potentially our comments change the active window size, so we
978 <i class="icon-flag-filled"></i>
1368 // notify sticky elements
979 <br/> ${len(c.unresolved_comments)}
1369 updateSticky()
980 </div>
1370 }
981
1371 }
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
1372
1076 ## TODO check why this ins't working
1373 var expandSidebar = function() {
1077 % if pull_request_menu:
1374 var $sideBar = $('.right-sidebar');
1078 <%
1375 $('.outerwrapper').css(marginExpanded);
1079 outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
1376 $('.header').css(marginExpandedHeader);
1080 %>
1377 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1081
1378 $('.right-sidebar-collapsed-state').hide();
1082 % if outdated_comm_count_ver:
1379 $('.right-sidebar-expanded-state').show();
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
1380
1121 <tr class="${_cls}" style="display: ${display}">
1381 $sideBar.addClass('right-sidebar-expanded')
1122 <td class="td-todo-number">
1382 $sideBar.removeClass('right-sidebar-collapsed')
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})">
1383 }
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
1384
1151 </div>
1385 var collapseSidebar = function() {
1152 </aside>
1386 var $sideBar = $('.right-sidebar');
1153
1154
1155 <script>
1156 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 $('.outerwrapper').css(marginExpanded);
1162 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1163 $('.right-sidebar-collapsed-state').hide();
1164 $('.right-sidebar-expanded-state').show();
1165 updateSticky()
1166
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()
1173 }
1174
1175 var toggleSidebar = function(){
1176 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 $('.outerwrapper').css(marginCollapsed);
1387 $('.outerwrapper').css(marginCollapsed);
1388 $('.header').css(marginCollapsedHeader);
1183 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
1389 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
1184 $('.right-sidebar-collapsed-state').show();
1390 $('.right-sidebar-collapsed-state').show();
1185 $('.right-sidebar-expanded-state').hide();
1391 $('.right-sidebar-expanded-state').hide();
1186
1392
1187 } else {
1393 $sideBar.removeClass('right-sidebar-expanded')
1188 // expand now
1394 $sideBar.addClass('right-sidebar-collapsed')
1189 $('.outerwrapper').css(marginExpanded);
1395 }
1190 $sideBar.addClass('right-sidebar-expanded')
1396
1191 $sideBar.removeClass('right-sidebar-collapsed')
1397 toggleSidebar = function () {
1192 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1398 var $sideBar = $('.right-sidebar');
1193 $('.right-sidebar-collapsed-state').hide();
1399
1194 $('.right-sidebar-expanded-state').show();
1400 if ($sideBar.hasClass('right-sidebar-expanded')) {
1401 // expanded -> collapsed transition
1402 collapseSidebar();
1403 var sidebarState = 'collapsed';
1404
1405 } else {
1406 // collapsed -> expanded
1407 expandSidebar();
1408 var sidebarState = 'expanded';
1409 }
1410
1411 // update our other sticky header in same context
1412 updateStickyHeader();
1413 storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
1195 }
1414 }
1196
1415
1197 // update our other sticky header in same context
1416 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1198 updateSticky()
1199 }
1200
1417
1201 var sidebarElement = document.getElementById('pr-nav-sticky');
1418 if (templateContext.session_attrs.sidebarState === 'expanded') {
1419 expanded = true
1420 } else if (templateContext.session_attrs.sidebarState === 'collapsed') {
1421 expanded = false
1422 }
1423
1424 // show sidebar since it's hidden on load
1425 $('.right-sidebar').show();
1202
1426
1203 ## sidebar = new StickySidebar(sidebarElement, {
1427 // init based on set initial class, or if defined user session attrs
1204 ## containerSelector: '.main',
1428 if (expanded) {
1205 ## minWidth: 62,
1429 expandSidebar();
1206 ## innerWrapperSelector: '.navbar__inner',
1430 updateStickyHeader();
1207 ## stickyClass: 'is-sticky',
1208 ## });
1209
1431
1210 </script>
1432 } else {
1433 collapseSidebar();
1434 updateStickyHeader();
1435 }
1436 var channel = '${c.pr_broadcast_channel}';
1437 new PullRequestPresenceController(channel)
1438
1439 })
1440 </script>
1441
1211 </%def>
1442 </%def>
General Comments 0
You need to be logged in to leave comments. Login now