@@ -0,0 +1,134 | |||||
|
1 | ## snippet for sidebar elements | |||
|
2 | ## usage: | |||
|
3 | ## <%namespace name="sidebar" file="/base/sidebar.mako"/> | |||
|
4 | ## ${sidebar.comments_table()} | |||
|
5 | <%namespace name="base" file="/base/base.mako"/> | |||
|
6 | ||||
|
7 | <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)"> | |||
|
8 | <% | |||
|
9 | if todo_comments: | |||
|
10 | cls_ = 'todos-content-table' | |||
|
11 | def sorter(entry): | |||
|
12 | user_id = entry.author.user_id | |||
|
13 | resolved = '1' if entry.resolved else '0' | |||
|
14 | if user_id == c.rhodecode_user.user_id: | |||
|
15 | # own comments first | |||
|
16 | user_id = 0 | |||
|
17 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |||
|
18 | else: | |||
|
19 | cls_ = 'comments-content-table' | |||
|
20 | def sorter(entry): | |||
|
21 | user_id = entry.author.user_id | |||
|
22 | return '{}'.format(str(entry.comment_id).zfill(10000)) | |||
|
23 | ||||
|
24 | existing_ids = existing_ids or [] | |||
|
25 | ||||
|
26 | %> | |||
|
27 | ||||
|
28 | <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}"> | |||
|
29 | ||||
|
30 | % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))): | |||
|
31 | <% | |||
|
32 | display = '' | |||
|
33 | _cls = '' | |||
|
34 | %> | |||
|
35 | ||||
|
36 | <% | |||
|
37 | comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) | |||
|
38 | prev_comment_ver_index = 0 | |||
|
39 | if loop_obj.previous: | |||
|
40 | prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', [])) | |||
|
41 | ||||
|
42 | ver_info = None | |||
|
43 | if getattr(c, 'versions', []): | |||
|
44 | ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None | |||
|
45 | %> | |||
|
46 | <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %> | |||
|
47 | <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %> | |||
|
48 | <% | |||
|
49 | if (prev_comment_ver_index > comment_ver_index): | |||
|
50 | comments_ver_divider = comment_ver_index | |||
|
51 | else: | |||
|
52 | comments_ver_divider = None | |||
|
53 | %> | |||
|
54 | ||||
|
55 | % if todo_comments: | |||
|
56 | % if comment_obj.resolved: | |||
|
57 | <% _cls = 'resolved-todo' %> | |||
|
58 | <% display = 'none' %> | |||
|
59 | % endif | |||
|
60 | % else: | |||
|
61 | ## SKIP TODOs we display them in other area | |||
|
62 | % if comment_obj.is_todo: | |||
|
63 | <% display = 'none' %> | |||
|
64 | % endif | |||
|
65 | ## Skip outdated comments | |||
|
66 | % if comment_obj.outdated: | |||
|
67 | <% display = 'none' %> | |||
|
68 | <% _cls = 'hidden-comment' %> | |||
|
69 | % endif | |||
|
70 | % endif | |||
|
71 | ||||
|
72 | % if not todo_comments and comments_ver_divider: | |||
|
73 | <tr class="old-comments-marker"> | |||
|
74 | <td colspan="3"> | |||
|
75 | % if ver_info: | |||
|
76 | <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code> | |||
|
77 | % else: | |||
|
78 | <code>v${comments_ver_divider}</code> | |||
|
79 | % endif | |||
|
80 | </td> | |||
|
81 | </tr> | |||
|
82 | ||||
|
83 | % endif | |||
|
84 | ||||
|
85 | <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}"> | |||
|
86 | <td class="td-todo-number"> | |||
|
87 | ||||
|
88 | <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink" | |||
|
89 | href="#comment-${comment_obj.comment_id}" | |||
|
90 | onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})"> | |||
|
91 | ||||
|
92 | <% | |||
|
93 | version_info = '' | |||
|
94 | if is_pr: | |||
|
95 | version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version') | |||
|
96 | %> | |||
|
97 | ||||
|
98 | % if todo_comments: | |||
|
99 | % if comment_obj.is_inline: | |||
|
100 | <i class="tooltip icon-code" title="Inline TODO comment${version_info}."></i> | |||
|
101 | % else: | |||
|
102 | <i class="tooltip icon-comment" title="General TODO comment${version_info}."></i> | |||
|
103 | % endif | |||
|
104 | % else: | |||
|
105 | % if comment_obj.outdated: | |||
|
106 | <i class="tooltip icon-comment-toggle" title="Inline Outdated made in v${comment_ver_index}."></i> | |||
|
107 | % elif comment_obj.is_inline: | |||
|
108 | <i class="tooltip icon-code" title="Inline comment${version_info}."></i> | |||
|
109 | % else: | |||
|
110 | <i class="tooltip icon-comment" title="General comment${version_info}."></i> | |||
|
111 | % endif | |||
|
112 | % endif | |||
|
113 | ||||
|
114 | </a> | |||
|
115 | ## NEW, since refresh | |||
|
116 | % if existing_ids and comment_obj.comment_id not in existing_ids: | |||
|
117 | <span class="tag">NEW</span> | |||
|
118 | % endif | |||
|
119 | </td> | |||
|
120 | ||||
|
121 | <td class="td-todo-gravatar"> | |||
|
122 | ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])} | |||
|
123 | </td> | |||
|
124 | <td class="todo-comment-text-wrapper"> | |||
|
125 | <div class="tooltip todo-comment-text timeago ${('todo-resolved' if comment_obj.resolved else '')} " title="${h.format_date(comment_obj.created_on)}" datetime="${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}"> | |||
|
126 | <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code> | |||
|
127 | </div> | |||
|
128 | </td> | |||
|
129 | </tr> | |||
|
130 | % endfor | |||
|
131 | ||||
|
132 | </table> | |||
|
133 | ||||
|
134 | </%def> No newline at end of file |
@@ -485,23 +485,10 class TestRepoCommitCommentsView(TestCon | |||||
485 |
|
485 | |||
486 |
|
486 | |||
487 | def assert_comment_links(response, comments, inline_comments): |
|
487 | def assert_comment_links(response, comments, inline_comments): | |
488 | if comments == 1: |
|
488 | response.mustcontain( | |
489 | comments_text = "%d General" % comments |
|
489 | '<span class="display-none" id="general-comments-count">{}</span>'.format(comments)) | |
490 | else: |
|
490 | response.mustcontain( | |
491 | comments_text = "%d General" % comments |
|
491 | '<span class="display-none" id="inline-comments-count">{}</span>'.format(inline_comments)) | |
492 |
|
||||
493 | if inline_comments == 1: |
|
|||
494 | inline_comments_text = "%d Inline" % inline_comments |
|
|||
495 | else: |
|
|||
496 | inline_comments_text = "%d Inline" % inline_comments |
|
|||
497 |
|
492 | |||
498 | if comments: |
|
|||
499 | response.mustcontain('<a href="#comments">%s</a>,' % comments_text) |
|
|||
500 | else: |
|
|||
501 | response.mustcontain(comments_text) |
|
|||
502 |
|
493 | |||
503 | if inline_comments: |
|
494 | ||
504 | response.mustcontain( |
|
|||
505 | 'id="inline-comments-counter">%s' % inline_comments_text) |
|
|||
506 | else: |
|
|||
507 | response.mustcontain(inline_comments_text) |
|
@@ -18,8 +18,8 | |||||
18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
20 |
|
20 | |||
21 |
|
||||
22 | import logging |
|
21 | import logging | |
|
22 | import collections | |||
23 |
|
23 | |||
24 | from pyramid.httpexceptions import ( |
|
24 | from pyramid.httpexceptions import ( | |
25 | HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict) |
|
25 | HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict) | |
@@ -34,14 +34,14 from rhodecode.apps.file_store.exception | |||||
34 | from rhodecode.lib import diffs, codeblocks |
|
34 | from rhodecode.lib import diffs, codeblocks | |
35 | from rhodecode.lib.auth import ( |
|
35 | from rhodecode.lib.auth import ( | |
36 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) |
|
36 | LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired) | |
37 |
|
37 | from rhodecode.lib.ext_json import json | ||
38 | from rhodecode.lib.compat import OrderedDict |
|
38 | from rhodecode.lib.compat import OrderedDict | |
39 | from rhodecode.lib.diffs import ( |
|
39 | from rhodecode.lib.diffs import ( | |
40 | cache_diff, load_cached_diff, diff_cache_exist, get_diff_context, |
|
40 | cache_diff, load_cached_diff, diff_cache_exist, get_diff_context, | |
41 | get_diff_whitespace_flag) |
|
41 | get_diff_whitespace_flag) | |
42 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch |
|
42 | from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch | |
43 | import rhodecode.lib.helpers as h |
|
43 | import rhodecode.lib.helpers as h | |
44 | from rhodecode.lib.utils2 import safe_unicode, str2bool |
|
44 | from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict | |
45 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
45 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
46 | from rhodecode.lib.vcs.exceptions import ( |
|
46 | from rhodecode.lib.vcs.exceptions import ( | |
47 | RepositoryError, CommitDoesNotExistError) |
|
47 | RepositoryError, CommitDoesNotExistError) | |
@@ -87,7 +87,6 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 |
|
||||
91 | # get ranges of commit ids if preset |
|
90 | # get ranges of commit ids if preset | |
92 | commit_range = commit_id_range.split('...')[:2] |
|
91 | commit_range = commit_id_range.split('...')[:2] | |
93 |
|
92 | |||
@@ -116,6 +115,7 class RepoCommitsView(RepoAppView): | |||||
116 | except Exception: |
|
115 | except Exception: | |
117 | log.exception("General failure") |
|
116 | log.exception("General failure") | |
118 | raise HTTPNotFound() |
|
117 | raise HTTPNotFound() | |
|
118 | single_commit = len(c.commit_ranges) == 1 | |||
119 |
|
119 | |||
120 | c.changes = OrderedDict() |
|
120 | c.changes = OrderedDict() | |
121 | c.lines_added = 0 |
|
121 | c.lines_added = 0 | |
@@ -129,23 +129,48 class RepoCommitsView(RepoAppView): | |||||
129 | c.inline_comments = [] |
|
129 | c.inline_comments = [] | |
130 | c.files = [] |
|
130 | c.files = [] | |
131 |
|
131 | |||
132 | c.statuses = [] |
|
|||
133 | c.comments = [] |
|
132 | c.comments = [] | |
134 | c.unresolved_comments = [] |
|
133 | c.unresolved_comments = [] | |
135 | c.resolved_comments = [] |
|
134 | c.resolved_comments = [] | |
136 | if len(c.commit_ranges) == 1: |
|
135 | ||
|
136 | # Single commit | |||
|
137 | if single_commit: | |||
137 | commit = c.commit_ranges[0] |
|
138 | commit = c.commit_ranges[0] | |
138 | c.comments = CommentsModel().get_comments( |
|
139 | c.comments = CommentsModel().get_comments( | |
139 | self.db_repo.repo_id, |
|
140 | self.db_repo.repo_id, | |
140 | revision=commit.raw_id) |
|
141 | revision=commit.raw_id) | |
141 | c.statuses.append(ChangesetStatusModel().get_status( |
|
142 | ||
142 | self.db_repo.repo_id, commit.raw_id)) |
|
|||
143 | # comments from PR |
|
143 | # comments from PR | |
144 | statuses = ChangesetStatusModel().get_statuses( |
|
144 | statuses = ChangesetStatusModel().get_statuses( | |
145 | self.db_repo.repo_id, commit.raw_id, |
|
145 | self.db_repo.repo_id, commit.raw_id, | |
146 | with_revisions=True) |
|
146 | with_revisions=True) | |
147 | prs = set(st.pull_request for st in statuses |
|
147 | ||
148 | if st.pull_request is not None) |
|
148 | prs = set() | |
|
149 | reviewers = list() | |||
|
150 | reviewers_duplicates = set() # to not have duplicates from multiple votes | |||
|
151 | for c_status in statuses: | |||
|
152 | ||||
|
153 | # extract associated pull-requests from votes | |||
|
154 | if c_status.pull_request: | |||
|
155 | prs.add(c_status.pull_request) | |||
|
156 | ||||
|
157 | # extract reviewers | |||
|
158 | _user_id = c_status.author.user_id | |||
|
159 | if _user_id not in reviewers_duplicates: | |||
|
160 | reviewers.append( | |||
|
161 | StrictAttributeDict({ | |||
|
162 | 'user': c_status.author, | |||
|
163 | ||||
|
164 | # fake attributed for commit, page that we don't have | |||
|
165 | # but we share the display with PR page | |||
|
166 | 'mandatory': False, | |||
|
167 | 'reasons': [], | |||
|
168 | 'rule_user_group_data': lambda: None | |||
|
169 | }) | |||
|
170 | ) | |||
|
171 | reviewers_duplicates.add(_user_id) | |||
|
172 | ||||
|
173 | c.allowed_reviewers = reviewers | |||
149 | # from associated statuses, check the pull requests, and |
|
174 | # from associated statuses, check the pull requests, and | |
150 | # show comments from them |
|
175 | # show comments from them | |
151 | for pr in prs: |
|
176 | for pr in prs: | |
@@ -156,6 +181,37 class RepoCommitsView(RepoAppView): | |||||
156 | c.resolved_comments = CommentsModel()\ |
|
181 | c.resolved_comments = CommentsModel()\ | |
157 | .get_commit_resolved_todos(commit.raw_id) |
|
182 | .get_commit_resolved_todos(commit.raw_id) | |
158 |
|
183 | |||
|
184 | c.inline_comments_flat = CommentsModel()\ | |||
|
185 | .get_commit_inline_comments(commit.raw_id) | |||
|
186 | ||||
|
187 | review_statuses = ChangesetStatusModel().aggregate_votes_by_user( | |||
|
188 | statuses, reviewers) | |||
|
189 | ||||
|
190 | c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED | |||
|
191 | ||||
|
192 | c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) | |||
|
193 | ||||
|
194 | for review_obj, member, reasons, mandatory, status in review_statuses: | |||
|
195 | member_reviewer = h.reviewer_as_json( | |||
|
196 | member, reasons=reasons, mandatory=mandatory, | |||
|
197 | user_group=None | |||
|
198 | ) | |||
|
199 | ||||
|
200 | current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED | |||
|
201 | member_reviewer['review_status'] = current_review_status | |||
|
202 | member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status) | |||
|
203 | member_reviewer['allowed_to_update'] = False | |||
|
204 | c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer) | |||
|
205 | ||||
|
206 | c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json) | |||
|
207 | ||||
|
208 | # NOTE(marcink): this uses the same voting logic as in pull-requests | |||
|
209 | c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses) | |||
|
210 | c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format( | |||
|
211 | c.repo_name, | |||
|
212 | commit.raw_id | |||
|
213 | ) | |||
|
214 | ||||
159 | diff = None |
|
215 | diff = None | |
160 | # Iterate over ranges (default commit view is always one commit) |
|
216 | # Iterate over ranges (default commit view is always one commit) | |
161 | for commit in c.commit_ranges: |
|
217 | for commit in c.commit_ranges: | |
@@ -397,6 +453,7 class RepoCommitsView(RepoAppView): | |||||
397 | } |
|
453 | } | |
398 | if comment: |
|
454 | if comment: | |
399 | c.co = comment |
|
455 | c.co = comment | |
|
456 | c.at_version_num = 0 | |||
400 | rendered_comment = render( |
|
457 | rendered_comment = render( | |
401 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
458 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
402 | self._get_template_context(c), self.request) |
|
459 | self._get_template_context(c), self.request) |
@@ -39,7 +39,7 from rhodecode.lib.ext_json import json | |||||
39 | from rhodecode.lib.auth import ( |
|
39 | from rhodecode.lib.auth import ( | |
40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, |
|
40 | LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator, | |
41 | NotAnonymous, CSRFRequired) |
|
41 | NotAnonymous, CSRFRequired) | |
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode |
|
42 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int | |
43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
43 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason | |
44 | from rhodecode.lib.vcs.exceptions import ( |
|
44 | from rhodecode.lib.vcs.exceptions import ( | |
45 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) |
|
45 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) | |
@@ -474,9 +474,6 class RepoPullRequestsView(RepoAppView, | |||||
474 |
|
474 | |||
475 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) |
|
475 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) | |
476 |
|
476 | |||
477 |
|
||||
478 |
|
||||
479 |
|
||||
480 | general_comments, inline_comments = \ |
|
477 | general_comments, inline_comments = \ | |
481 | self.register_comments_vars(c, pull_request_latest, versions) |
|
478 | self.register_comments_vars(c, pull_request_latest, versions) | |
482 |
|
479 | |||
@@ -980,7 +977,7 class RepoPullRequestsView(RepoAppView, | |||||
980 | version = self.request.GET.get('version') |
|
977 | version = self.request.GET.get('version') | |
981 |
|
978 | |||
982 | _render = self.request.get_partial_renderer( |
|
979 | _render = self.request.get_partial_renderer( | |
983 |
'rhodecode:templates/ |
|
980 | 'rhodecode:templates/base/sidebar.mako') | |
984 | c = _render.get_call_context() |
|
981 | c = _render.get_call_context() | |
985 |
|
982 | |||
986 | (pull_request_latest, |
|
983 | (pull_request_latest, | |
@@ -999,7 +996,11 class RepoPullRequestsView(RepoAppView, | |||||
999 |
|
996 | |||
1000 | self.register_comments_vars(c, pull_request_latest, versions) |
|
997 | self.register_comments_vars(c, pull_request_latest, versions) | |
1001 | all_comments = c.inline_comments_flat + c.comments |
|
998 | all_comments = c.inline_comments_flat + c.comments | |
1002 | return _render('comments_table', all_comments, len(all_comments)) |
|
999 | ||
|
1000 | existing_ids = filter( | |||
|
1001 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |||
|
1002 | return _render('comments_table', all_comments, len(all_comments), | |||
|
1003 | existing_ids=existing_ids) | |||
1003 |
|
1004 | |||
1004 | @LoginRequired() |
|
1005 | @LoginRequired() | |
1005 | @NotAnonymous() |
|
1006 | @NotAnonymous() | |
@@ -1017,7 +1018,7 class RepoPullRequestsView(RepoAppView, | |||||
1017 | version = self.request.GET.get('version') |
|
1018 | version = self.request.GET.get('version') | |
1018 |
|
1019 | |||
1019 | _render = self.request.get_partial_renderer( |
|
1020 | _render = self.request.get_partial_renderer( | |
1020 |
'rhodecode:templates/ |
|
1021 | 'rhodecode:templates/base/sidebar.mako') | |
1021 | c = _render.get_call_context() |
|
1022 | c = _render.get_call_context() | |
1022 | (pull_request_latest, |
|
1023 | (pull_request_latest, | |
1023 | pull_request_at_ver, |
|
1024 | pull_request_at_ver, | |
@@ -1039,7 +1040,10 class RepoPullRequestsView(RepoAppView, | |||||
1039 | .get_pull_request_resolved_todos(pull_request) |
|
1040 | .get_pull_request_resolved_todos(pull_request) | |
1040 |
|
1041 | |||
1041 | all_comments = c.unresolved_comments + c.resolved_comments |
|
1042 | all_comments = c.unresolved_comments + c.resolved_comments | |
1042 | return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True) |
|
1043 | existing_ids = filter( | |
|
1044 | lambda e: e, map(safe_int, self.request.POST.getall('comments[]'))) | |||
|
1045 | return _render('comments_table', all_comments, len(c.unresolved_comments), | |||
|
1046 | todo_comments=True, existing_ids=existing_ids) | |||
1043 |
|
1047 | |||
1044 | @LoginRequired() |
|
1048 | @LoginRequired() | |
1045 | @NotAnonymous() |
|
1049 | @NotAnonymous() |
@@ -354,34 +354,37 class ChangesetStatusModel(BaseModel): | |||||
354 | Session().add(new_status) |
|
354 | Session().add(new_status) | |
355 | return new_statuses |
|
355 | return new_statuses | |
356 |
|
356 | |||
|
357 | def aggregate_votes_by_user(self, commit_statuses, reviewers_data): | |||
|
358 | ||||
|
359 | commit_statuses_map = collections.defaultdict(list) | |||
|
360 | for st in commit_statuses: | |||
|
361 | commit_statuses_map[st.author.username] += [st] | |||
|
362 | ||||
|
363 | reviewers = [] | |||
|
364 | ||||
|
365 | def version(commit_status): | |||
|
366 | return commit_status.version | |||
|
367 | ||||
|
368 | for obj in reviewers_data: | |||
|
369 | if not obj.user: | |||
|
370 | continue | |||
|
371 | statuses = commit_statuses_map.get(obj.user.username, None) | |||
|
372 | if statuses: | |||
|
373 | status_groups = itertools.groupby( | |||
|
374 | sorted(statuses, key=version), version) | |||
|
375 | statuses = [(x, list(y)[0]) for x, y in status_groups] | |||
|
376 | ||||
|
377 | reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses)) | |||
|
378 | ||||
|
379 | return reviewers | |||
|
380 | ||||
357 | def reviewers_statuses(self, pull_request): |
|
381 | def reviewers_statuses(self, pull_request): | |
358 | _commit_statuses = self.get_statuses( |
|
382 | _commit_statuses = self.get_statuses( | |
359 | pull_request.source_repo, |
|
383 | pull_request.source_repo, | |
360 | pull_request=pull_request, |
|
384 | pull_request=pull_request, | |
361 | with_revisions=True) |
|
385 | with_revisions=True) | |
362 |
|
386 | |||
363 | commit_statuses = collections.defaultdict(list) |
|
387 | return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers) | |
364 | for st in _commit_statuses: |
|
|||
365 | commit_statuses[st.author.username] += [st] |
|
|||
366 |
|
||||
367 | pull_request_reviewers = [] |
|
|||
368 |
|
||||
369 | def version(commit_status): |
|
|||
370 | return commit_status.version |
|
|||
371 |
|
||||
372 | for obj in pull_request.reviewers: |
|
|||
373 | if not obj.user: |
|
|||
374 | continue |
|
|||
375 | statuses = commit_statuses.get(obj.user.username, None) |
|
|||
376 | if statuses: |
|
|||
377 | status_groups = itertools.groupby( |
|
|||
378 | sorted(statuses, key=version), version) |
|
|||
379 | statuses = [(x, list(y)[0]) for x, y in status_groups] |
|
|||
380 |
|
||||
381 | pull_request_reviewers.append( |
|
|||
382 | (obj, obj.user, obj.reasons, obj.mandatory, statuses)) |
|
|||
383 |
|
||||
384 | return pull_request_reviewers |
|
|||
385 |
|
388 | |||
386 | def calculated_review_status(self, pull_request, reviewers_statuses=None): |
|
389 | def calculated_review_status(self, pull_request, reviewers_statuses=None): | |
387 | """ |
|
390 | """ |
@@ -228,6 +228,14 class CommentsModel(BaseModel): | |||||
228 |
|
228 | |||
229 | return todos |
|
229 | return todos | |
230 |
|
230 | |||
|
231 | def get_commit_inline_comments(self, commit_id): | |||
|
232 | inline_comments = Session().query(ChangesetComment) \ | |||
|
233 | .filter(ChangesetComment.line_no != None) \ | |||
|
234 | .filter(ChangesetComment.f_path != None) \ | |||
|
235 | .filter(ChangesetComment.revision == commit_id) | |||
|
236 | inline_comments = inline_comments.all() | |||
|
237 | return inline_comments | |||
|
238 | ||||
231 | def _log_audit_action(self, action, action_data, auth_user, comment): |
|
239 | def _log_audit_action(self, action, action_data, auth_user, comment): | |
232 | audit_logger.store( |
|
240 | audit_logger.store( | |
233 | action=action, |
|
241 | action=action, |
@@ -55,3 +55,16 | |||||
55 | margin: 0 auto 35px auto; |
|
55 | margin: 0 auto 35px auto; | |
56 | } |
|
56 | } | |
57 | } |
|
57 | } | |
|
58 | ||||
|
59 | .alert-text-success { | |||
|
60 | color: @alert1; | |||
|
61 | ||||
|
62 | } | |||
|
63 | ||||
|
64 | .alert-text-error { | |||
|
65 | color: @alert2; | |||
|
66 | } | |||
|
67 | ||||
|
68 | .alert-text-warning { | |||
|
69 | color: @alert3; | |||
|
70 | } |
@@ -254,7 +254,7 input[type="button"] { | |||||
254 |
|
254 | |||
255 | .btn-group-actions { |
|
255 | .btn-group-actions { | |
256 | position: relative; |
|
256 | position: relative; | |
257 |
z-index: |
|
257 | z-index: 50; | |
258 |
|
258 | |||
259 | &:not(.open) .btn-action-switcher-container { |
|
259 | &:not(.open) .btn-action-switcher-container { | |
260 | display: none; |
|
260 | display: none; |
@@ -1078,10 +1078,16 input.filediff-collapse-state { | |||||
1078 | background: @color5; |
|
1078 | background: @color5; | |
1079 | color: white; |
|
1079 | color: white; | |
1080 | } |
|
1080 | } | |
|
1081 | ||||
1081 | &[op="comments"] { /* comments on file */ |
|
1082 | &[op="comments"] { /* comments on file */ | |
1082 | background: @grey4; |
|
1083 | background: @grey4; | |
1083 | color: white; |
|
1084 | color: white; | |
1084 | } |
|
1085 | } | |
|
1086 | ||||
|
1087 | &[op="options"] { /* context menu */ | |||
|
1088 | background: @grey6; | |||
|
1089 | color: black; | |||
|
1090 | } | |||
1085 | } |
|
1091 | } | |
1086 | } |
|
1092 | } | |
1087 |
|
1093 |
@@ -31,6 +31,10 a { cursor: pointer; } | |||||
31 | clear: both; |
|
31 | clear: both; | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
|
34 | .display-none { | |||
|
35 | display: none; | |||
|
36 | } | |||
|
37 | ||||
34 | .pull-right { |
|
38 | .pull-right { | |
35 | float: right !important; |
|
39 | float: right !important; | |
36 | } |
|
40 | } |
@@ -83,6 +83,11 body { | |||||
83 | } |
|
83 | } | |
84 | } |
|
84 | } | |
85 |
|
85 | |||
|
86 | .flex-container { | |||
|
87 | display: flex; | |||
|
88 | justify-content: space-between; | |||
|
89 | } | |||
|
90 | ||||
86 | .action-link{ |
|
91 | .action-link{ | |
87 | margin-left: @padding; |
|
92 | margin-left: @padding; | |
88 | padding-left: @padding; |
|
93 | padding-left: @padding; | |
@@ -482,6 +487,15 ul.auth_plugins { | |||||
482 | text-align: left; |
|
487 | text-align: left; | |
483 | overflow: hidden; |
|
488 | overflow: hidden; | |
484 | white-space: pre-line; |
|
489 | white-space: pre-line; | |
|
490 | padding-top: 5px | |||
|
491 | } | |||
|
492 | ||||
|
493 | #add_reviewer { | |||
|
494 | padding-top: 10px; | |||
|
495 | } | |||
|
496 | ||||
|
497 | #add_reviewer_input { | |||
|
498 | padding-top: 10px | |||
485 | } |
|
499 | } | |
486 |
|
500 | |||
487 | .pr-details-title-author-pref { |
|
501 | .pr-details-title-author-pref { | |
@@ -1169,9 +1183,12 label { | |||||
1169 | a { |
|
1183 | a { | |
1170 | color: @grey5 |
|
1184 | color: @grey5 | |
1171 | } |
|
1185 | } | |
1172 | @media screen and (max-width: 1200px) { |
|
1186 | ||
|
1187 | // 1024px or smaller | |||
|
1188 | @media screen and (max-width: 1180px) { | |||
1173 | display: none; |
|
1189 | display: none; | |
1174 | } |
|
1190 | } | |
|
1191 | ||||
1175 | } |
|
1192 | } | |
1176 |
|
1193 | |||
1177 | img { |
|
1194 | img { | |
@@ -1553,6 +1570,7 table.integrations { | |||||
1553 | width: 16px; |
|
1570 | width: 16px; | |
1554 | padding: 0; |
|
1571 | padding: 0; | |
1555 | color: black; |
|
1572 | color: black; | |
|
1573 | cursor: pointer; | |||
1556 | } |
|
1574 | } | |
1557 |
|
1575 | |||
1558 | .reviewer_member_mandatory_remove { |
|
1576 | .reviewer_member_mandatory_remove { | |
@@ -1682,7 +1700,7 table.group_members { | |||||
1682 | } |
|
1700 | } | |
1683 |
|
1701 | |||
1684 | .reviewer_ac .ac-input { |
|
1702 | .reviewer_ac .ac-input { | |
1685 |
width: |
|
1703 | width: 100%; | |
1686 | margin-bottom: 1em; |
|
1704 | margin-bottom: 1em; | |
1687 | } |
|
1705 | } | |
1688 |
|
1706 | |||
@@ -2756,7 +2774,7 table.rctable td.td-search-results div { | |||||
2756 | } |
|
2774 | } | |
2757 |
|
2775 | |||
2758 | #help_kb .modal-content{ |
|
2776 | #help_kb .modal-content{ | |
2759 |
max-width: |
|
2777 | max-width: 800px; | |
2760 | margin: 10% auto; |
|
2778 | margin: 10% auto; | |
2761 |
|
2779 | |||
2762 | table{ |
|
2780 | table{ | |
@@ -3053,4 +3071,141 form.markup-form { | |||||
3053 |
|
3071 | |||
3054 | .pr-hovercard-title { |
|
3072 | .pr-hovercard-title { | |
3055 | padding-top: 5px; |
|
3073 | padding-top: 5px; | |
3056 | } No newline at end of file |
|
3074 | } | |
|
3075 | ||||
|
3076 | .action-divider { | |||
|
3077 | opacity: 0.5; | |||
|
3078 | } | |||
|
3079 | ||||
|
3080 | .details-inline-block { | |||
|
3081 | display: inline-block; | |||
|
3082 | position: relative; | |||
|
3083 | } | |||
|
3084 | ||||
|
3085 | .details-inline-block summary { | |||
|
3086 | list-style: none; | |||
|
3087 | } | |||
|
3088 | ||||
|
3089 | details:not([open]) > :not(summary) { | |||
|
3090 | display: none !important; | |||
|
3091 | } | |||
|
3092 | ||||
|
3093 | .details-reset > summary { | |||
|
3094 | list-style: none; | |||
|
3095 | } | |||
|
3096 | ||||
|
3097 | .details-reset > summary::-webkit-details-marker { | |||
|
3098 | display: none; | |||
|
3099 | } | |||
|
3100 | ||||
|
3101 | .details-dropdown { | |||
|
3102 | position: absolute; | |||
|
3103 | top: 100%; | |||
|
3104 | width: 185px; | |||
|
3105 | list-style: none; | |||
|
3106 | background-color: #fff; | |||
|
3107 | background-clip: padding-box; | |||
|
3108 | border: 1px solid @grey5; | |||
|
3109 | box-shadow: 0 8px 24px rgba(149, 157, 165, .2); | |||
|
3110 | left: -150px; | |||
|
3111 | text-align: left; | |||
|
3112 | z-index: 90; | |||
|
3113 | } | |||
|
3114 | ||||
|
3115 | .dropdown-divider { | |||
|
3116 | display: block; | |||
|
3117 | height: 0; | |||
|
3118 | margin: 8px 0; | |||
|
3119 | border-top: 1px solid @grey5; | |||
|
3120 | } | |||
|
3121 | ||||
|
3122 | .dropdown-item { | |||
|
3123 | display: block; | |||
|
3124 | padding: 4px 8px 4px 16px; | |||
|
3125 | overflow: hidden; | |||
|
3126 | text-overflow: ellipsis; | |||
|
3127 | white-space: nowrap; | |||
|
3128 | font-weight: normal; | |||
|
3129 | } | |||
|
3130 | ||||
|
3131 | .right-sidebar { | |||
|
3132 | position: fixed; | |||
|
3133 | top: 0px; | |||
|
3134 | bottom: 0; | |||
|
3135 | right: 0; | |||
|
3136 | ||||
|
3137 | background: #fafafa; | |||
|
3138 | z-index: 50; | |||
|
3139 | } | |||
|
3140 | ||||
|
3141 | .right-sidebar { | |||
|
3142 | border-left: 1px solid @grey5; | |||
|
3143 | } | |||
|
3144 | ||||
|
3145 | .right-sidebar.right-sidebar-expanded { | |||
|
3146 | width: 300px; | |||
|
3147 | overflow: scroll; | |||
|
3148 | } | |||
|
3149 | ||||
|
3150 | .right-sidebar.right-sidebar-collapsed { | |||
|
3151 | width: 40px; | |||
|
3152 | padding: 0; | |||
|
3153 | display: block; | |||
|
3154 | overflow: hidden; | |||
|
3155 | } | |||
|
3156 | ||||
|
3157 | .sidenav { | |||
|
3158 | float: right; | |||
|
3159 | will-change: min-height; | |||
|
3160 | background: #fafafa; | |||
|
3161 | width: 100%; | |||
|
3162 | } | |||
|
3163 | ||||
|
3164 | .sidebar-toggle { | |||
|
3165 | height: 30px; | |||
|
3166 | text-align: center; | |||
|
3167 | margin: 15px 0px 0 0; | |||
|
3168 | } | |||
|
3169 | ||||
|
3170 | .sidebar-toggle a { | |||
|
3171 | ||||
|
3172 | } | |||
|
3173 | ||||
|
3174 | .sidebar-content { | |||
|
3175 | margin-left: 15px; | |||
|
3176 | margin-right: 15px; | |||
|
3177 | } | |||
|
3178 | ||||
|
3179 | .sidebar-heading { | |||
|
3180 | font-size: 1.2em; | |||
|
3181 | font-weight: 700; | |||
|
3182 | margin-top: 10px; | |||
|
3183 | } | |||
|
3184 | ||||
|
3185 | .sidebar-element { | |||
|
3186 | margin-top: 20px; | |||
|
3187 | } | |||
|
3188 | ||||
|
3189 | .right-sidebar-collapsed-state { | |||
|
3190 | display: flex; | |||
|
3191 | flex-direction: column; | |||
|
3192 | justify-content: center; | |||
|
3193 | align-items: center; | |||
|
3194 | padding: 0 10px; | |||
|
3195 | cursor: pointer; | |||
|
3196 | font-size: 1.3em; | |||
|
3197 | margin: 0 -15px; | |||
|
3198 | } | |||
|
3199 | ||||
|
3200 | .right-sidebar-collapsed-state:hover { | |||
|
3201 | background-color: @grey5; | |||
|
3202 | } | |||
|
3203 | ||||
|
3204 | .old-comments-marker { | |||
|
3205 | text-align: left; | |||
|
3206 | } | |||
|
3207 | ||||
|
3208 | .old-comments-marker td { | |||
|
3209 | padding-top: 15px; | |||
|
3210 | border-bottom: 1px solid @grey5; | |||
|
3211 | } |
@@ -790,7 +790,7 input { | |||||
790 |
|
790 | |||
791 | &.main_filter_input { |
|
791 | &.main_filter_input { | |
792 | padding: 5px 10px; |
|
792 | padding: 5px 10px; | |
793 | min-width: 340px; |
|
793 | ||
794 | color: @grey7; |
|
794 | color: @grey7; | |
795 | background: @black; |
|
795 | background: @black; | |
796 | min-height: 18px; |
|
796 | min-height: 18px; | |
@@ -800,11 +800,34 input { | |||||
800 | color: @grey2 !important; |
|
800 | color: @grey2 !important; | |
801 | background: white !important; |
|