##// END OF EJS Templates
sidebar: few fixes for panel rendering of reviewers/observers for both commits and PRS.
marcink -
r4503:3bcf2943 stable
parent child Browse files
Show More
@@ -1,781 +1,784 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
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 import logging
21 import logging
22 import collections
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)
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.apps.file_store import utils as store_utils
31 from rhodecode.apps.file_store import utils as store_utils
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33
33
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 from rhodecode.lib.ext_json import json
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, StrictAttributeDict
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)
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
49 ChangesetCommentHistory
49 ChangesetCommentHistory
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.comment import CommentsModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 def _update_with_GET(params, request):
58 def _update_with_GET(params, request):
59 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += request.GET.getall(k)
60 params[k] += request.GET.getall(k)
61
61
62
62
63 class RepoCommitsView(RepoAppView):
63 class RepoCommitsView(RepoAppView):
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context(include_app_defaults=True)
65 c = self._get_local_tmpl_context(include_app_defaults=True)
66 c.rhodecode_repo = self.rhodecode_vcs_repo
66 c.rhodecode_repo = self.rhodecode_vcs_repo
67
67
68 return c
68 return c
69
69
70 def _is_diff_cache_enabled(self, target_repo):
70 def _is_diff_cache_enabled(self, target_repo):
71 caching_enabled = self._get_general_setting(
71 caching_enabled = self._get_general_setting(
72 target_repo, 'rhodecode_diff_cache')
72 target_repo, 'rhodecode_diff_cache')
73 log.debug('Diff caching enabled: %s', caching_enabled)
73 log.debug('Diff caching enabled: %s', caching_enabled)
74 return caching_enabled
74 return caching_enabled
75
75
76 def _commit(self, commit_id_range, method):
76 def _commit(self, commit_id_range, method):
77 _ = self.request.translate
77 _ = self.request.translate
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.fulldiff = self.request.GET.get('fulldiff')
79 c.fulldiff = self.request.GET.get('fulldiff')
80
80
81 # fetch global flags of ignore ws or context lines
81 # fetch global flags of ignore ws or context lines
82 diff_context = get_diff_context(self.request)
82 diff_context = get_diff_context(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84
84
85 # diff_limit will cut off the whole diff if the limit is applied
85 # diff_limit will cut off the whole diff if the limit is applied
86 # otherwise it will just hide the big files from the front-end
86 # otherwise it will just hide the big files from the front-end
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 # get ranges of commit ids if preset
90 # get ranges of commit ids if preset
91 commit_range = commit_id_range.split('...')[:2]
91 commit_range = commit_id_range.split('...')[:2]
92
92
93 try:
93 try:
94 pre_load = ['affected_files', 'author', 'branch', 'date',
94 pre_load = ['affected_files', 'author', 'branch', 'date',
95 'message', 'parents']
95 'message', 'parents']
96 if self.rhodecode_vcs_repo.alias == 'hg':
96 if self.rhodecode_vcs_repo.alias == 'hg':
97 pre_load += ['hidden', 'obsolete', 'phase']
97 pre_load += ['hidden', 'obsolete', 'phase']
98
98
99 if len(commit_range) == 2:
99 if len(commit_range) == 2:
100 commits = self.rhodecode_vcs_repo.get_commits(
100 commits = self.rhodecode_vcs_repo.get_commits(
101 start_id=commit_range[0], end_id=commit_range[1],
101 start_id=commit_range[0], end_id=commit_range[1],
102 pre_load=pre_load, translate_tags=False)
102 pre_load=pre_load, translate_tags=False)
103 commits = list(commits)
103 commits = list(commits)
104 else:
104 else:
105 commits = [self.rhodecode_vcs_repo.get_commit(
105 commits = [self.rhodecode_vcs_repo.get_commit(
106 commit_id=commit_id_range, pre_load=pre_load)]
106 commit_id=commit_id_range, pre_load=pre_load)]
107
107
108 c.commit_ranges = commits
108 c.commit_ranges = commits
109 if not c.commit_ranges:
109 if not c.commit_ranges:
110 raise RepositoryError('The commit range returned an empty result')
110 raise RepositoryError('The commit range returned an empty result')
111 except CommitDoesNotExistError as e:
111 except CommitDoesNotExistError as e:
112 msg = _('No such commit exists. Org exception: `{}`').format(e)
112 msg = _('No such commit exists. Org exception: `{}`').format(e)
113 h.flash(msg, category='error')
113 h.flash(msg, category='error')
114 raise HTTPNotFound()
114 raise HTTPNotFound()
115 except Exception:
115 except Exception:
116 log.exception("General failure")
116 log.exception("General failure")
117 raise HTTPNotFound()
117 raise HTTPNotFound()
118 single_commit = len(c.commit_ranges) == 1
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
122 c.lines_deleted = 0
122 c.lines_deleted = 0
123
123
124 # auto collapse if we have more than limit
124 # auto collapse if we have more than limit
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
127
127
128 c.commit_statuses = ChangesetStatus.STATUSES
128 c.commit_statuses = ChangesetStatus.STATUSES
129 c.inline_comments = []
129 c.inline_comments = []
130 c.files = []
130 c.files = []
131
131
132 c.comments = []
132 c.comments = []
133 c.unresolved_comments = []
133 c.unresolved_comments = []
134 c.resolved_comments = []
134 c.resolved_comments = []
135
135
136 # Single commit
136 # Single commit
137 if single_commit:
137 if single_commit:
138 commit = c.commit_ranges[0]
138 commit = c.commit_ranges[0]
139 c.comments = CommentsModel().get_comments(
139 c.comments = CommentsModel().get_comments(
140 self.db_repo.repo_id,
140 self.db_repo.repo_id,
141 revision=commit.raw_id)
141 revision=commit.raw_id)
142
142
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
147
148 prs = set()
148 prs = set()
149 reviewers = list()
149 reviewers = list()
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
151 for c_status in statuses:
151 for c_status in statuses:
152
152
153 # extract associated pull-requests from votes
153 # extract associated pull-requests from votes
154 if c_status.pull_request:
154 if c_status.pull_request:
155 prs.add(c_status.pull_request)
155 prs.add(c_status.pull_request)
156
156
157 # extract reviewers
157 # extract reviewers
158 _user_id = c_status.author.user_id
158 _user_id = c_status.author.user_id
159 if _user_id not in reviewers_duplicates:
159 if _user_id not in reviewers_duplicates:
160 reviewers.append(
160 reviewers.append(
161 StrictAttributeDict({
161 StrictAttributeDict({
162 'user': c_status.author,
162 'user': c_status.author,
163
163
164 # fake attributed for commit, page that we don't have
164 # fake attributed for commit, page that we don't have
165 # but we share the display with PR page
165 # but we share the display with PR page
166 'mandatory': False,
166 'mandatory': False,
167 'reasons': [],
167 'reasons': [],
168 'rule_user_group_data': lambda: None
168 'rule_user_group_data': lambda: None
169 })
169 })
170 )
170 )
171 reviewers_duplicates.add(_user_id)
171 reviewers_duplicates.add(_user_id)
172
172
173 c.allowed_reviewers = reviewers
173 c.allowed_reviewers = reviewers
174 c.reviewers_count = len(reviewers)
175 c.observers_count = 0
176
174 # from associated statuses, check the pull requests, and
177 # from associated statuses, check the pull requests, and
175 # show comments from them
178 # show comments from them
176 for pr in prs:
179 for pr in prs:
177 c.comments.extend(pr.comments)
180 c.comments.extend(pr.comments)
178
181
179 c.unresolved_comments = CommentsModel()\
182 c.unresolved_comments = CommentsModel()\
180 .get_commit_unresolved_todos(commit.raw_id)
183 .get_commit_unresolved_todos(commit.raw_id)
181 c.resolved_comments = CommentsModel()\
184 c.resolved_comments = CommentsModel()\
182 .get_commit_resolved_todos(commit.raw_id)
185 .get_commit_resolved_todos(commit.raw_id)
183
186
184 c.inline_comments_flat = CommentsModel()\
187 c.inline_comments_flat = CommentsModel()\
185 .get_commit_inline_comments(commit.raw_id)
188 .get_commit_inline_comments(commit.raw_id)
186
189
187 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
190 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
188 statuses, reviewers)
191 statuses, reviewers)
189
192
190 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
193 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
191
194
192 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
195 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
193
196
194 for review_obj, member, reasons, mandatory, status in review_statuses:
197 for review_obj, member, reasons, mandatory, status in review_statuses:
195 member_reviewer = h.reviewer_as_json(
198 member_reviewer = h.reviewer_as_json(
196 member, reasons=reasons, mandatory=mandatory, role=None,
199 member, reasons=reasons, mandatory=mandatory, role=None,
197 user_group=None
200 user_group=None
198 )
201 )
199
202
200 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
203 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
201 member_reviewer['review_status'] = current_review_status
204 member_reviewer['review_status'] = current_review_status
202 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
205 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
203 member_reviewer['allowed_to_update'] = False
206 member_reviewer['allowed_to_update'] = False
204 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
207 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
205
208
206 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
209 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
207
210
208 # NOTE(marcink): this uses the same voting logic as in pull-requests
211 # NOTE(marcink): this uses the same voting logic as in pull-requests
209 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
212 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
210 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
213 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
211 c.repo_name,
214 c.repo_name,
212 commit.raw_id
215 commit.raw_id
213 )
216 )
214
217
215 diff = None
218 diff = None
216 # Iterate over ranges (default commit view is always one commit)
219 # Iterate over ranges (default commit view is always one commit)
217 for commit in c.commit_ranges:
220 for commit in c.commit_ranges:
218 c.changes[commit.raw_id] = []
221 c.changes[commit.raw_id] = []
219
222
220 commit2 = commit
223 commit2 = commit
221 commit1 = commit.first_parent
224 commit1 = commit.first_parent
222
225
223 if method == 'show':
226 if method == 'show':
224 inline_comments = CommentsModel().get_inline_comments(
227 inline_comments = CommentsModel().get_inline_comments(
225 self.db_repo.repo_id, revision=commit.raw_id)
228 self.db_repo.repo_id, revision=commit.raw_id)
226 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
229 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
227 inline_comments))
230 inline_comments))
228 c.inline_comments = inline_comments
231 c.inline_comments = inline_comments
229
232
230 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
233 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
231 self.db_repo)
234 self.db_repo)
232 cache_file_path = diff_cache_exist(
235 cache_file_path = diff_cache_exist(
233 cache_path, 'diff', commit.raw_id,
236 cache_path, 'diff', commit.raw_id,
234 hide_whitespace_changes, diff_context, c.fulldiff)
237 hide_whitespace_changes, diff_context, c.fulldiff)
235
238
236 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
239 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
237 force_recache = str2bool(self.request.GET.get('force_recache'))
240 force_recache = str2bool(self.request.GET.get('force_recache'))
238
241
239 cached_diff = None
242 cached_diff = None
240 if caching_enabled:
243 if caching_enabled:
241 cached_diff = load_cached_diff(cache_file_path)
244 cached_diff = load_cached_diff(cache_file_path)
242
245
243 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
246 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
244 if not force_recache and has_proper_diff_cache:
247 if not force_recache and has_proper_diff_cache:
245 diffset = cached_diff['diff']
248 diffset = cached_diff['diff']
246 else:
249 else:
247 vcs_diff = self.rhodecode_vcs_repo.get_diff(
250 vcs_diff = self.rhodecode_vcs_repo.get_diff(
248 commit1, commit2,
251 commit1, commit2,
249 ignore_whitespace=hide_whitespace_changes,
252 ignore_whitespace=hide_whitespace_changes,
250 context=diff_context)
253 context=diff_context)
251
254
252 diff_processor = diffs.DiffProcessor(
255 diff_processor = diffs.DiffProcessor(
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
256 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 file_limit=file_limit, show_full_diff=c.fulldiff)
257 file_limit=file_limit, show_full_diff=c.fulldiff)
255
258
256 _parsed = diff_processor.prepare()
259 _parsed = diff_processor.prepare()
257
260
258 diffset = codeblocks.DiffSet(
261 diffset = codeblocks.DiffSet(
259 repo_name=self.db_repo_name,
262 repo_name=self.db_repo_name,
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
263 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
264 target_node_getter=codeblocks.diffset_node_getter(commit2))
262
265
263 diffset = self.path_filter.render_patchset_filtered(
266 diffset = self.path_filter.render_patchset_filtered(
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
267 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265
268
266 # save cached diff
269 # save cached diff
267 if caching_enabled:
270 if caching_enabled:
268 cache_diff(cache_file_path, diffset, None)
271 cache_diff(cache_file_path, diffset, None)
269
272
270 c.limited_diff = diffset.limited_diff
273 c.limited_diff = diffset.limited_diff
271 c.changes[commit.raw_id] = diffset
274 c.changes[commit.raw_id] = diffset
272 else:
275 else:
273 # TODO(marcink): no cache usage here...
276 # TODO(marcink): no cache usage here...
274 _diff = self.rhodecode_vcs_repo.get_diff(
277 _diff = self.rhodecode_vcs_repo.get_diff(
275 commit1, commit2,
278 commit1, commit2,
276 ignore_whitespace=hide_whitespace_changes, context=diff_context)
279 ignore_whitespace=hide_whitespace_changes, context=diff_context)
277 diff_processor = diffs.DiffProcessor(
280 diff_processor = diffs.DiffProcessor(
278 _diff, format='newdiff', diff_limit=diff_limit,
281 _diff, format='newdiff', diff_limit=diff_limit,
279 file_limit=file_limit, show_full_diff=c.fulldiff)
282 file_limit=file_limit, show_full_diff=c.fulldiff)
280 # downloads/raw we only need RAW diff nothing else
283 # downloads/raw we only need RAW diff nothing else
281 diff = self.path_filter.get_raw_patch(diff_processor)
284 diff = self.path_filter.get_raw_patch(diff_processor)
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
285 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
283
286
284 # sort comments by how they were generated
287 # sort comments by how they were generated
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
288 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
286 c.at_version_num = None
289 c.at_version_num = None
287
290
288 if len(c.commit_ranges) == 1:
291 if len(c.commit_ranges) == 1:
289 c.commit = c.commit_ranges[0]
292 c.commit = c.commit_ranges[0]
290 c.parent_tmpl = ''.join(
293 c.parent_tmpl = ''.join(
291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
294 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
292
295
293 if method == 'download':
296 if method == 'download':
294 response = Response(diff)
297 response = Response(diff)
295 response.content_type = 'text/plain'
298 response.content_type = 'text/plain'
296 response.content_disposition = (
299 response.content_disposition = (
297 'attachment; filename=%s.diff' % commit_id_range[:12])
300 'attachment; filename=%s.diff' % commit_id_range[:12])
298 return response
301 return response
299 elif method == 'patch':
302 elif method == 'patch':
300 c.diff = safe_unicode(diff)
303 c.diff = safe_unicode(diff)
301 patch = render(
304 patch = render(
302 'rhodecode:templates/changeset/patch_changeset.mako',
305 'rhodecode:templates/changeset/patch_changeset.mako',
303 self._get_template_context(c), self.request)
306 self._get_template_context(c), self.request)
304 response = Response(patch)
307 response = Response(patch)
305 response.content_type = 'text/plain'
308 response.content_type = 'text/plain'
306 return response
309 return response
307 elif method == 'raw':
310 elif method == 'raw':
308 response = Response(diff)
311 response = Response(diff)
309 response.content_type = 'text/plain'
312 response.content_type = 'text/plain'
310 return response
313 return response
311 elif method == 'show':
314 elif method == 'show':
312 if len(c.commit_ranges) == 1:
315 if len(c.commit_ranges) == 1:
313 html = render(
316 html = render(
314 'rhodecode:templates/changeset/changeset.mako',
317 'rhodecode:templates/changeset/changeset.mako',
315 self._get_template_context(c), self.request)
318 self._get_template_context(c), self.request)
316 return Response(html)
319 return Response(html)
317 else:
320 else:
318 c.ancestor = None
321 c.ancestor = None
319 c.target_repo = self.db_repo
322 c.target_repo = self.db_repo
320 html = render(
323 html = render(
321 'rhodecode:templates/changeset/changeset_range.mako',
324 'rhodecode:templates/changeset/changeset_range.mako',
322 self._get_template_context(c), self.request)
325 self._get_template_context(c), self.request)
323 return Response(html)
326 return Response(html)
324
327
325 raise HTTPBadRequest()
328 raise HTTPBadRequest()
326
329
327 @LoginRequired()
330 @LoginRequired()
328 @HasRepoPermissionAnyDecorator(
331 @HasRepoPermissionAnyDecorator(
329 'repository.read', 'repository.write', 'repository.admin')
332 'repository.read', 'repository.write', 'repository.admin')
330 @view_config(
333 @view_config(
331 route_name='repo_commit', request_method='GET',
334 route_name='repo_commit', request_method='GET',
332 renderer=None)
335 renderer=None)
333 def repo_commit_show(self):
336 def repo_commit_show(self):
334 commit_id = self.request.matchdict['commit_id']
337 commit_id = self.request.matchdict['commit_id']
335 return self._commit(commit_id, method='show')
338 return self._commit(commit_id, method='show')
336
339
337 @LoginRequired()
340 @LoginRequired()
338 @HasRepoPermissionAnyDecorator(
341 @HasRepoPermissionAnyDecorator(
339 'repository.read', 'repository.write', 'repository.admin')
342 'repository.read', 'repository.write', 'repository.admin')
340 @view_config(
343 @view_config(
341 route_name='repo_commit_raw', request_method='GET',
344 route_name='repo_commit_raw', request_method='GET',
342 renderer=None)
345 renderer=None)
343 @view_config(
346 @view_config(
344 route_name='repo_commit_raw_deprecated', request_method='GET',
347 route_name='repo_commit_raw_deprecated', request_method='GET',
345 renderer=None)
348 renderer=None)
346 def repo_commit_raw(self):
349 def repo_commit_raw(self):
347 commit_id = self.request.matchdict['commit_id']
350 commit_id = self.request.matchdict['commit_id']
348 return self._commit(commit_id, method='raw')
351 return self._commit(commit_id, method='raw')
349
352
350 @LoginRequired()
353 @LoginRequired()
351 @HasRepoPermissionAnyDecorator(
354 @HasRepoPermissionAnyDecorator(
352 'repository.read', 'repository.write', 'repository.admin')
355 'repository.read', 'repository.write', 'repository.admin')
353 @view_config(
356 @view_config(
354 route_name='repo_commit_patch', request_method='GET',
357 route_name='repo_commit_patch', request_method='GET',
355 renderer=None)
358 renderer=None)
356 def repo_commit_patch(self):
359 def repo_commit_patch(self):
357 commit_id = self.request.matchdict['commit_id']
360 commit_id = self.request.matchdict['commit_id']
358 return self._commit(commit_id, method='patch')
361 return self._commit(commit_id, method='patch')
359
362
360 @LoginRequired()
363 @LoginRequired()
361 @HasRepoPermissionAnyDecorator(
364 @HasRepoPermissionAnyDecorator(
362 'repository.read', 'repository.write', 'repository.admin')
365 'repository.read', 'repository.write', 'repository.admin')
363 @view_config(
366 @view_config(
364 route_name='repo_commit_download', request_method='GET',
367 route_name='repo_commit_download', request_method='GET',
365 renderer=None)
368 renderer=None)
366 def repo_commit_download(self):
369 def repo_commit_download(self):
367 commit_id = self.request.matchdict['commit_id']
370 commit_id = self.request.matchdict['commit_id']
368 return self._commit(commit_id, method='download')
371 return self._commit(commit_id, method='download')
369
372
370 @LoginRequired()
373 @LoginRequired()
371 @NotAnonymous()
374 @NotAnonymous()
372 @HasRepoPermissionAnyDecorator(
375 @HasRepoPermissionAnyDecorator(
373 'repository.read', 'repository.write', 'repository.admin')
376 'repository.read', 'repository.write', 'repository.admin')
374 @CSRFRequired()
377 @CSRFRequired()
375 @view_config(
378 @view_config(
376 route_name='repo_commit_comment_create', request_method='POST',
379 route_name='repo_commit_comment_create', request_method='POST',
377 renderer='json_ext')
380 renderer='json_ext')
378 def repo_commit_comment_create(self):
381 def repo_commit_comment_create(self):
379 _ = self.request.translate
382 _ = self.request.translate
380 commit_id = self.request.matchdict['commit_id']
383 commit_id = self.request.matchdict['commit_id']
381
384
382 c = self.load_default_context()
385 c = self.load_default_context()
383 status = self.request.POST.get('changeset_status', None)
386 status = self.request.POST.get('changeset_status', None)
384 text = self.request.POST.get('text')
387 text = self.request.POST.get('text')
385 comment_type = self.request.POST.get('comment_type')
388 comment_type = self.request.POST.get('comment_type')
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
389 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
387
390
388 if status:
391 if status:
389 text = text or (_('Status change %(transition_icon)s %(status)s')
392 text = text or (_('Status change %(transition_icon)s %(status)s')
390 % {'transition_icon': '>',
393 % {'transition_icon': '>',
391 'status': ChangesetStatus.get_status_lbl(status)})
394 'status': ChangesetStatus.get_status_lbl(status)})
392
395
393 multi_commit_ids = []
396 multi_commit_ids = []
394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
397 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
398 if _commit_id not in ['', None, EmptyCommit.raw_id]:
396 if _commit_id not in multi_commit_ids:
399 if _commit_id not in multi_commit_ids:
397 multi_commit_ids.append(_commit_id)
400 multi_commit_ids.append(_commit_id)
398
401
399 commit_ids = multi_commit_ids or [commit_id]
402 commit_ids = multi_commit_ids or [commit_id]
400
403
401 comment = None
404 comment = None
402 for current_id in filter(None, commit_ids):
405 for current_id in filter(None, commit_ids):
403 comment = CommentsModel().create(
406 comment = CommentsModel().create(
404 text=text,
407 text=text,
405 repo=self.db_repo.repo_id,
408 repo=self.db_repo.repo_id,
406 user=self._rhodecode_db_user.user_id,
409 user=self._rhodecode_db_user.user_id,
407 commit_id=current_id,
410 commit_id=current_id,
408 f_path=self.request.POST.get('f_path'),
411 f_path=self.request.POST.get('f_path'),
409 line_no=self.request.POST.get('line'),
412 line_no=self.request.POST.get('line'),
410 status_change=(ChangesetStatus.get_status_lbl(status)
413 status_change=(ChangesetStatus.get_status_lbl(status)
411 if status else None),
414 if status else None),
412 status_change_type=status,
415 status_change_type=status,
413 comment_type=comment_type,
416 comment_type=comment_type,
414 resolves_comment_id=resolves_comment_id,
417 resolves_comment_id=resolves_comment_id,
415 auth_user=self._rhodecode_user
418 auth_user=self._rhodecode_user
416 )
419 )
417
420
418 # get status if set !
421 # get status if set !
419 if status:
422 if status:
420 # if latest status was from pull request and it's closed
423 # if latest status was from pull request and it's closed
421 # disallow changing status !
424 # disallow changing status !
422 # dont_allow_on_closed_pull_request = True !
425 # dont_allow_on_closed_pull_request = True !
423
426
424 try:
427 try:
425 ChangesetStatusModel().set_status(
428 ChangesetStatusModel().set_status(
426 self.db_repo.repo_id,
429 self.db_repo.repo_id,
427 status,
430 status,
428 self._rhodecode_db_user.user_id,
431 self._rhodecode_db_user.user_id,
429 comment,
432 comment,
430 revision=current_id,
433 revision=current_id,
431 dont_allow_on_closed_pull_request=True
434 dont_allow_on_closed_pull_request=True
432 )
435 )
433 except StatusChangeOnClosedPullRequestError:
436 except StatusChangeOnClosedPullRequestError:
434 msg = _('Changing the status of a commit associated with '
437 msg = _('Changing the status of a commit associated with '
435 'a closed pull request is not allowed')
438 'a closed pull request is not allowed')
436 log.exception(msg)
439 log.exception(msg)
437 h.flash(msg, category='warning')
440 h.flash(msg, category='warning')
438 raise HTTPFound(h.route_path(
441 raise HTTPFound(h.route_path(
439 'repo_commit', repo_name=self.db_repo_name,
442 'repo_commit', repo_name=self.db_repo_name,
440 commit_id=current_id))
443 commit_id=current_id))
441
444
442 commit = self.db_repo.get_commit(current_id)
445 commit = self.db_repo.get_commit(current_id)
443 CommentsModel().trigger_commit_comment_hook(
446 CommentsModel().trigger_commit_comment_hook(
444 self.db_repo, self._rhodecode_user, 'create',
447 self.db_repo, self._rhodecode_user, 'create',
445 data={'comment': comment, 'commit': commit})
448 data={'comment': comment, 'commit': commit})
446
449
447 # finalize, commit and redirect
450 # finalize, commit and redirect
448 Session().commit()
451 Session().commit()
449
452
450 data = {
453 data = {
451 'target_id': h.safeid(h.safe_unicode(
454 'target_id': h.safeid(h.safe_unicode(
452 self.request.POST.get('f_path'))),
455 self.request.POST.get('f_path'))),
453 }
456 }
454 if comment:
457 if comment:
455 c.co = comment
458 c.co = comment
456 c.at_version_num = 0
459 c.at_version_num = 0
457 rendered_comment = render(
460 rendered_comment = render(
458 'rhodecode:templates/changeset/changeset_comment_block.mako',
461 'rhodecode:templates/changeset/changeset_comment_block.mako',
459 self._get_template_context(c), self.request)
462 self._get_template_context(c), self.request)
460
463
461 data.update(comment.get_dict())
464 data.update(comment.get_dict())
462 data.update({'rendered_text': rendered_comment})
465 data.update({'rendered_text': rendered_comment})
463
466
464 return data
467 return data
465
468
466 @LoginRequired()
469 @LoginRequired()
467 @NotAnonymous()
470 @NotAnonymous()
468 @HasRepoPermissionAnyDecorator(
471 @HasRepoPermissionAnyDecorator(
469 'repository.read', 'repository.write', 'repository.admin')
472 'repository.read', 'repository.write', 'repository.admin')
470 @CSRFRequired()
473 @CSRFRequired()
471 @view_config(
474 @view_config(
472 route_name='repo_commit_comment_preview', request_method='POST',
475 route_name='repo_commit_comment_preview', request_method='POST',
473 renderer='string', xhr=True)
476 renderer='string', xhr=True)
474 def repo_commit_comment_preview(self):
477 def repo_commit_comment_preview(self):
475 # Technically a CSRF token is not needed as no state changes with this
478 # Technically a CSRF token is not needed as no state changes with this
476 # call. However, as this is a POST is better to have it, so automated
479 # call. However, as this is a POST is better to have it, so automated
477 # tools don't flag it as potential CSRF.
480 # tools don't flag it as potential CSRF.
478 # Post is required because the payload could be bigger than the maximum
481 # Post is required because the payload could be bigger than the maximum
479 # allowed by GET.
482 # allowed by GET.
480
483
481 text = self.request.POST.get('text')
484 text = self.request.POST.get('text')
482 renderer = self.request.POST.get('renderer') or 'rst'
485 renderer = self.request.POST.get('renderer') or 'rst'
483 if text:
486 if text:
484 return h.render(text, renderer=renderer, mentions=True,
487 return h.render(text, renderer=renderer, mentions=True,
485 repo_name=self.db_repo_name)
488 repo_name=self.db_repo_name)
486 return ''
489 return ''
487
490
488 @LoginRequired()
491 @LoginRequired()
489 @HasRepoPermissionAnyDecorator(
492 @HasRepoPermissionAnyDecorator(
490 'repository.read', 'repository.write', 'repository.admin')
493 'repository.read', 'repository.write', 'repository.admin')
491 @CSRFRequired()
494 @CSRFRequired()
492 @view_config(
495 @view_config(
493 route_name='repo_commit_comment_history_view', request_method='POST',
496 route_name='repo_commit_comment_history_view', request_method='POST',
494 renderer='string', xhr=True)
497 renderer='string', xhr=True)
495 def repo_commit_comment_history_view(self):
498 def repo_commit_comment_history_view(self):
496 c = self.load_default_context()
499 c = self.load_default_context()
497
500
498 comment_history_id = self.request.matchdict['comment_history_id']
501 comment_history_id = self.request.matchdict['comment_history_id']
499 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
502 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
500 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
503 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
501
504
502 if is_repo_comment:
505 if is_repo_comment:
503 c.comment_history = comment_history
506 c.comment_history = comment_history
504
507
505 rendered_comment = render(
508 rendered_comment = render(
506 'rhodecode:templates/changeset/comment_history.mako',
509 'rhodecode:templates/changeset/comment_history.mako',
507 self._get_template_context(c)
510 self._get_template_context(c)
508 , self.request)
511 , self.request)
509 return rendered_comment
512 return rendered_comment
510 else:
513 else:
511 log.warning('No permissions for user %s to show comment_history_id: %s',
514 log.warning('No permissions for user %s to show comment_history_id: %s',
512 self._rhodecode_db_user, comment_history_id)
515 self._rhodecode_db_user, comment_history_id)
513 raise HTTPNotFound()
516 raise HTTPNotFound()
514
517
515 @LoginRequired()
518 @LoginRequired()
516 @NotAnonymous()
519 @NotAnonymous()
517 @HasRepoPermissionAnyDecorator(
520 @HasRepoPermissionAnyDecorator(
518 'repository.read', 'repository.write', 'repository.admin')
521 'repository.read', 'repository.write', 'repository.admin')
519 @CSRFRequired()
522 @CSRFRequired()
520 @view_config(
523 @view_config(
521 route_name='repo_commit_comment_attachment_upload', request_method='POST',
524 route_name='repo_commit_comment_attachment_upload', request_method='POST',
522 renderer='json_ext', xhr=True)
525 renderer='json_ext', xhr=True)
523 def repo_commit_comment_attachment_upload(self):
526 def repo_commit_comment_attachment_upload(self):
524 c = self.load_default_context()
527 c = self.load_default_context()
525 upload_key = 'attachment'
528 upload_key = 'attachment'
526
529
527 file_obj = self.request.POST.get(upload_key)
530 file_obj = self.request.POST.get(upload_key)
528
531
529 if file_obj is None:
532 if file_obj is None:
530 self.request.response.status = 400
533 self.request.response.status = 400
531 return {'store_fid': None,
534 return {'store_fid': None,
532 'access_path': None,
535 'access_path': None,
533 'error': '{} data field is missing'.format(upload_key)}
536 'error': '{} data field is missing'.format(upload_key)}
534
537
535 if not hasattr(file_obj, 'filename'):
538 if not hasattr(file_obj, 'filename'):
536 self.request.response.status = 400
539 self.request.response.status = 400
537 return {'store_fid': None,
540 return {'store_fid': None,
538 'access_path': None,
541 'access_path': None,
539 'error': 'filename cannot be read from the data field'}
542 'error': 'filename cannot be read from the data field'}
540
543
541 filename = file_obj.filename
544 filename = file_obj.filename
542 file_display_name = filename
545 file_display_name = filename
543
546
544 metadata = {
547 metadata = {
545 'user_uploaded': {'username': self._rhodecode_user.username,
548 'user_uploaded': {'username': self._rhodecode_user.username,
546 'user_id': self._rhodecode_user.user_id,
549 'user_id': self._rhodecode_user.user_id,
547 'ip': self._rhodecode_user.ip_addr}}
550 'ip': self._rhodecode_user.ip_addr}}
548
551
549 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
552 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
550 allowed_extensions = [
553 allowed_extensions = [
551 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
554 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
552 '.pptx', '.txt', '.xlsx', '.zip']
555 '.pptx', '.txt', '.xlsx', '.zip']
553 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
556 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
554
557
555 try:
558 try:
556 storage = store_utils.get_file_storage(self.request.registry.settings)
559 storage = store_utils.get_file_storage(self.request.registry.settings)
557 store_uid, metadata = storage.save_file(
560 store_uid, metadata = storage.save_file(
558 file_obj.file, filename, extra_metadata=metadata,
561 file_obj.file, filename, extra_metadata=metadata,
559 extensions=allowed_extensions, max_filesize=max_file_size)
562 extensions=allowed_extensions, max_filesize=max_file_size)
560 except FileNotAllowedException:
563 except FileNotAllowedException:
561 self.request.response.status = 400
564 self.request.response.status = 400
562 permitted_extensions = ', '.join(allowed_extensions)
565 permitted_extensions = ', '.join(allowed_extensions)
563 error_msg = 'File `{}` is not allowed. ' \
566 error_msg = 'File `{}` is not allowed. ' \
564 'Only following extensions are permitted: {}'.format(
567 'Only following extensions are permitted: {}'.format(
565 filename, permitted_extensions)
568 filename, permitted_extensions)
566 return {'store_fid': None,
569 return {'store_fid': None,
567 'access_path': None,
570 'access_path': None,
568 'error': error_msg}
571 'error': error_msg}
569 except FileOverSizeException:
572 except FileOverSizeException:
570 self.request.response.status = 400
573 self.request.response.status = 400
571 limit_mb = h.format_byte_size_binary(max_file_size)
574 limit_mb = h.format_byte_size_binary(max_file_size)
572 return {'store_fid': None,
575 return {'store_fid': None,
573 'access_path': None,
576 'access_path': None,
574 'error': 'File {} is exceeding allowed limit of {}.'.format(
577 'error': 'File {} is exceeding allowed limit of {}.'.format(
575 filename, limit_mb)}
578 filename, limit_mb)}
576
579
577 try:
580 try:
578 entry = FileStore.create(
581 entry = FileStore.create(
579 file_uid=store_uid, filename=metadata["filename"],
582 file_uid=store_uid, filename=metadata["filename"],
580 file_hash=metadata["sha256"], file_size=metadata["size"],
583 file_hash=metadata["sha256"], file_size=metadata["size"],
581 file_display_name=file_display_name,
584 file_display_name=file_display_name,
582 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
585 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
583 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
586 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
584 scope_repo_id=self.db_repo.repo_id
587 scope_repo_id=self.db_repo.repo_id
585 )
588 )
586 Session().add(entry)
589 Session().add(entry)
587 Session().commit()
590 Session().commit()
588 log.debug('Stored upload in DB as %s', entry)
591 log.debug('Stored upload in DB as %s', entry)
589 except Exception:
592 except Exception:
590 log.exception('Failed to store file %s', filename)
593 log.exception('Failed to store file %s', filename)
591 self.request.response.status = 400
594 self.request.response.status = 400
592 return {'store_fid': None,
595 return {'store_fid': None,
593 'access_path': None,
596 'access_path': None,
594 'error': 'File {} failed to store in DB.'.format(filename)}
597 'error': 'File {} failed to store in DB.'.format(filename)}
595
598
596 Session().commit()
599 Session().commit()
597
600
598 return {
601 return {
599 'store_fid': store_uid,
602 'store_fid': store_uid,
600 'access_path': h.route_path(
603 'access_path': h.route_path(
601 'download_file', fid=store_uid),
604 'download_file', fid=store_uid),
602 'fqn_access_path': h.route_url(
605 'fqn_access_path': h.route_url(
603 'download_file', fid=store_uid),
606 'download_file', fid=store_uid),
604 'repo_access_path': h.route_path(
607 'repo_access_path': h.route_path(
605 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
608 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
606 'repo_fqn_access_path': h.route_url(
609 'repo_fqn_access_path': h.route_url(
607 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
610 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
608 }
611 }
609
612
610 @LoginRequired()
613 @LoginRequired()
611 @NotAnonymous()
614 @NotAnonymous()
612 @HasRepoPermissionAnyDecorator(
615 @HasRepoPermissionAnyDecorator(
613 'repository.read', 'repository.write', 'repository.admin')
616 'repository.read', 'repository.write', 'repository.admin')
614 @CSRFRequired()
617 @CSRFRequired()
615 @view_config(
618 @view_config(
616 route_name='repo_commit_comment_delete', request_method='POST',
619 route_name='repo_commit_comment_delete', request_method='POST',
617 renderer='json_ext')
620 renderer='json_ext')
618 def repo_commit_comment_delete(self):
621 def repo_commit_comment_delete(self):
619 commit_id = self.request.matchdict['commit_id']
622 commit_id = self.request.matchdict['commit_id']
620 comment_id = self.request.matchdict['comment_id']
623 comment_id = self.request.matchdict['comment_id']
621
624
622 comment = ChangesetComment.get_or_404(comment_id)
625 comment = ChangesetComment.get_or_404(comment_id)
623 if not comment:
626 if not comment:
624 log.debug('Comment with id:%s not found, skipping', comment_id)
627 log.debug('Comment with id:%s not found, skipping', comment_id)
625 # comment already deleted in another call probably
628 # comment already deleted in another call probably
626 return True
629 return True
627
630
628 if comment.immutable:
631 if comment.immutable:
629 # don't allow deleting comments that are immutable
632 # don't allow deleting comments that are immutable
630 raise HTTPForbidden()
633 raise HTTPForbidden()
631
634
632 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
635 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
633 super_admin = h.HasPermissionAny('hg.admin')()
636 super_admin = h.HasPermissionAny('hg.admin')()
634 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
637 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
635 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
638 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
636 comment_repo_admin = is_repo_admin and is_repo_comment
639 comment_repo_admin = is_repo_admin and is_repo_comment
637
640
638 if super_admin or comment_owner or comment_repo_admin:
641 if super_admin or comment_owner or comment_repo_admin:
639 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
642 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
640 Session().commit()
643 Session().commit()
641 return True
644 return True
642 else:
645 else:
643 log.warning('No permissions for user %s to delete comment_id: %s',
646 log.warning('No permissions for user %s to delete comment_id: %s',
644 self._rhodecode_db_user, comment_id)
647 self._rhodecode_db_user, comment_id)
645 raise HTTPNotFound()
648 raise HTTPNotFound()
646
649
647 @LoginRequired()
650 @LoginRequired()
648 @NotAnonymous()
651 @NotAnonymous()
649 @HasRepoPermissionAnyDecorator(
652 @HasRepoPermissionAnyDecorator(
650 'repository.read', 'repository.write', 'repository.admin')
653 'repository.read', 'repository.write', 'repository.admin')
651 @CSRFRequired()
654 @CSRFRequired()
652 @view_config(
655 @view_config(
653 route_name='repo_commit_comment_edit', request_method='POST',
656 route_name='repo_commit_comment_edit', request_method='POST',
654 renderer='json_ext')
657 renderer='json_ext')
655 def repo_commit_comment_edit(self):
658 def repo_commit_comment_edit(self):
656 self.load_default_context()
659 self.load_default_context()
657
660
658 comment_id = self.request.matchdict['comment_id']
661 comment_id = self.request.matchdict['comment_id']
659 comment = ChangesetComment.get_or_404(comment_id)
662 comment = ChangesetComment.get_or_404(comment_id)
660
663
661 if comment.immutable:
664 if comment.immutable:
662 # don't allow deleting comments that are immutable
665 # don't allow deleting comments that are immutable
663 raise HTTPForbidden()
666 raise HTTPForbidden()
664
667
665 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
668 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
666 super_admin = h.HasPermissionAny('hg.admin')()
669 super_admin = h.HasPermissionAny('hg.admin')()
667 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
670 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
668 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
671 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
669 comment_repo_admin = is_repo_admin and is_repo_comment
672 comment_repo_admin = is_repo_admin and is_repo_comment
670
673
671 if super_admin or comment_owner or comment_repo_admin:
674 if super_admin or comment_owner or comment_repo_admin:
672 text = self.request.POST.get('text')
675 text = self.request.POST.get('text')
673 version = self.request.POST.get('version')
676 version = self.request.POST.get('version')
674 if text == comment.text:
677 if text == comment.text:
675 log.warning(
678 log.warning(
676 'Comment(repo): '
679 'Comment(repo): '
677 'Trying to create new version '
680 'Trying to create new version '
678 'with the same comment body {}'.format(
681 'with the same comment body {}'.format(
679 comment_id,
682 comment_id,
680 )
683 )
681 )
684 )
682 raise HTTPNotFound()
685 raise HTTPNotFound()
683
686
684 if version.isdigit():
687 if version.isdigit():
685 version = int(version)
688 version = int(version)
686 else:
689 else:
687 log.warning(
690 log.warning(
688 'Comment(repo): Wrong version type {} {} '
691 'Comment(repo): Wrong version type {} {} '
689 'for comment {}'.format(
692 'for comment {}'.format(
690 version,
693 version,
691 type(version),
694 type(version),
692 comment_id,
695 comment_id,
693 )
696 )
694 )
697 )
695 raise HTTPNotFound()
698 raise HTTPNotFound()
696
699
697 try:
700 try:
698 comment_history = CommentsModel().edit(
701 comment_history = CommentsModel().edit(
699 comment_id=comment_id,
702 comment_id=comment_id,
700 text=text,
703 text=text,
701 auth_user=self._rhodecode_user,
704 auth_user=self._rhodecode_user,
702 version=version,
705 version=version,
703 )
706 )
704 except CommentVersionMismatch:
707 except CommentVersionMismatch:
705 raise HTTPConflict()
708 raise HTTPConflict()
706
709
707 if not comment_history:
710 if not comment_history:
708 raise HTTPNotFound()
711 raise HTTPNotFound()
709
712
710 commit_id = self.request.matchdict['commit_id']
713 commit_id = self.request.matchdict['commit_id']
711 commit = self.db_repo.get_commit(commit_id)
714 commit = self.db_repo.get_commit(commit_id)
712 CommentsModel().trigger_commit_comment_hook(
715 CommentsModel().trigger_commit_comment_hook(
713 self.db_repo, self._rhodecode_user, 'edit',
716 self.db_repo, self._rhodecode_user, 'edit',
714 data={'comment': comment, 'commit': commit})
717 data={'comment': comment, 'commit': commit})
715
718
716 Session().commit()
719 Session().commit()
717 return {
720 return {
718 'comment_history_id': comment_history.comment_history_id,
721 'comment_history_id': comment_history.comment_history_id,
719 'comment_id': comment.comment_id,
722 'comment_id': comment.comment_id,
720 'comment_version': comment_history.version,
723 'comment_version': comment_history.version,
721 'comment_author_username': comment_history.author.username,
724 'comment_author_username': comment_history.author.username,
722 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
725 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
723 'comment_created_on': h.age_component(comment_history.created_on,
726 'comment_created_on': h.age_component(comment_history.created_on,
724 time_is_local=True),
727 time_is_local=True),
725 }
728 }
726 else:
729 else:
727 log.warning('No permissions for user %s to edit comment_id: %s',
730 log.warning('No permissions for user %s to edit comment_id: %s',
728 self._rhodecode_db_user, comment_id)
731 self._rhodecode_db_user, comment_id)
729 raise HTTPNotFound()
732 raise HTTPNotFound()
730
733
731 @LoginRequired()
734 @LoginRequired()
732 @HasRepoPermissionAnyDecorator(
735 @HasRepoPermissionAnyDecorator(
733 'repository.read', 'repository.write', 'repository.admin')
736 'repository.read', 'repository.write', 'repository.admin')
734 @view_config(
737 @view_config(
735 route_name='repo_commit_data', request_method='GET',
738 route_name='repo_commit_data', request_method='GET',
736 renderer='json_ext', xhr=True)
739 renderer='json_ext', xhr=True)
737 def repo_commit_data(self):
740 def repo_commit_data(self):
738 commit_id = self.request.matchdict['commit_id']
741 commit_id = self.request.matchdict['commit_id']
739 self.load_default_context()
742 self.load_default_context()
740
743
741 try:
744 try:
742 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
745 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
743 except CommitDoesNotExistError as e:
746 except CommitDoesNotExistError as e:
744 return EmptyCommit(message=str(e))
747 return EmptyCommit(message=str(e))
745
748
746 @LoginRequired()
749 @LoginRequired()
747 @HasRepoPermissionAnyDecorator(
750 @HasRepoPermissionAnyDecorator(
748 'repository.read', 'repository.write', 'repository.admin')
751 'repository.read', 'repository.write', 'repository.admin')
749 @view_config(
752 @view_config(
750 route_name='repo_commit_children', request_method='GET',
753 route_name='repo_commit_children', request_method='GET',
751 renderer='json_ext', xhr=True)
754 renderer='json_ext', xhr=True)
752 def repo_commit_children(self):
755 def repo_commit_children(self):
753 commit_id = self.request.matchdict['commit_id']
756 commit_id = self.request.matchdict['commit_id']
754 self.load_default_context()
757 self.load_default_context()
755
758
756 try:
759 try:
757 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
760 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
758 children = commit.children
761 children = commit.children
759 except CommitDoesNotExistError:
762 except CommitDoesNotExistError:
760 children = []
763 children = []
761
764
762 result = {"results": children}
765 result = {"results": children}
763 return result
766 return result
764
767
765 @LoginRequired()
768 @LoginRequired()
766 @HasRepoPermissionAnyDecorator(
769 @HasRepoPermissionAnyDecorator(
767 'repository.read', 'repository.write', 'repository.admin')
770 'repository.read', 'repository.write', 'repository.admin')
768 @view_config(
771 @view_config(
769 route_name='repo_commit_parents', request_method='GET',
772 route_name='repo_commit_parents', request_method='GET',
770 renderer='json_ext')
773 renderer='json_ext')
771 def repo_commit_parents(self):
774 def repo_commit_parents(self):
772 commit_id = self.request.matchdict['commit_id']
775 commit_id = self.request.matchdict['commit_id']
773 self.load_default_context()
776 self.load_default_context()
774
777
775 try:
778 try:
776 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
779 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
777 parents = commit.parents
780 parents = commit.parents
778 except CommitDoesNotExistError:
781 except CommitDoesNotExistError:
779 parents = []
782 parents = []
780 result = {"results": parents}
783 result = {"results": parents}
781 return result
784 return result
@@ -1,1175 +1,1184 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#pr_submit').attr('disabled', 'disabled');
44 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#pr_submit').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = value['message'];
78 var rawMessage = value['message'];
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 var rawMessage = elements[0]['message'];
83 var rawMessage = elements[0]['message'];
84 title = rawMessage.split('\n')[0];
84 title = rawMessage.split('\n')[0];
85 }
85 }
86 else {
86 else {
87 // use reference name
87 // use reference name
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
89 var refType = sourceRefType;
89 var refType = sourceRefType;
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
91 }
91 }
92
92
93 return [title, desc]
93 return [title, desc]
94 };
94 };
95
95
96
96
97 window.ReviewersController = function () {
97 window.ReviewersController = function () {
98 var self = this;
98 var self = this;
99 this.$loadingIndicator = $('.calculate-reviewers');
99 this.$loadingIndicator = $('.calculate-reviewers');
100 this.$reviewRulesContainer = $('#review_rules');
100 this.$reviewRulesContainer = $('#review_rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
102 this.$userRule = $('.pr-user-rule-container');
102 this.$userRule = $('.pr-user-rule-container');
103 this.$reviewMembers = $('#review_members');
103 this.$reviewMembers = $('#review_members');
104 this.$observerMembers = $('#observer_members');
104 this.$observerMembers = $('#observer_members');
105
105
106 this.currentRequest = null;
106 this.currentRequest = null;
107 this.diffData = null;
107 this.diffData = null;
108 this.enabledRules = [];
108 this.enabledRules = [];
109 // sync with db.py entries
109 // sync with db.py entries
110 this.ROLE_REVIEWER = 'reviewer';
110 this.ROLE_REVIEWER = 'reviewer';
111 this.ROLE_OBSERVER = 'observer'
111 this.ROLE_OBSERVER = 'observer'
112
112
113 //dummy handler, we might register our own later
113 //dummy handler, we might register our own later
114 this.diffDataHandler = function (data) {};
114 this.diffDataHandler = function (data) {};
115
115
116 this.defaultForbidUsers = function () {
116 this.defaultForbidUsers = function () {
117 return [
117 return [
118 {
118 {
119 'username': 'default',
119 'username': 'default',
120 'user_id': templateContext.default_user.user_id
120 'user_id': templateContext.default_user.user_id
121 }
121 }
122 ];
122 ];
123 };
123 };
124
124
125 // init default forbidden users
125 // init default forbidden users
126 this.forbidUsers = this.defaultForbidUsers();
126 this.forbidUsers = this.defaultForbidUsers();
127
127
128 this.hideReviewRules = function () {
128 this.hideReviewRules = function () {
129 self.$reviewRulesContainer.hide();
129 self.$reviewRulesContainer.hide();
130 $(self.$userRule.selector).hide();
130 $(self.$userRule.selector).hide();
131 };
131 };
132
132
133 this.showReviewRules = function () {
133 this.showReviewRules = function () {
134 self.$reviewRulesContainer.show();
134 self.$reviewRulesContainer.show();
135 $(self.$userRule.selector).show();
135 $(self.$userRule.selector).show();
136 };
136 };
137
137
138 this.addRule = function (ruleText) {
138 this.addRule = function (ruleText) {
139 self.showReviewRules();
139 self.showReviewRules();
140 self.enabledRules.push(ruleText);
140 self.enabledRules.push(ruleText);
141 return '<div>- {0}</div>'.format(ruleText)
141 return '<div>- {0}</div>'.format(ruleText)
142 };
142 };
143
143
144 this.increaseCounter = function(role) {
144 this.increaseCounter = function(role) {
145 if (role === self.ROLE_REVIEWER) {
145 if (role === self.ROLE_REVIEWER) {
146 var $elem = $('#reviewers-cnt')
146 var $elem = $('#reviewers-cnt')
147 var cnt = parseInt($elem.data('count') || 0)
147 var cnt = parseInt($elem.data('count') || 0)
148 cnt +=1
148 cnt +=1
149 $elem.html(cnt);
149 $elem.html(cnt);
150 $elem.data('count', cnt);
150 $elem.data('count', cnt);
151 }
151 }
152 else if (role === self.ROLE_OBSERVER) {
152 else if (role === self.ROLE_OBSERVER) {
153 var $elem = $('#observers-cnt');
153 var $elem = $('#observers-cnt');
154 var cnt = parseInt($elem.data('count') || 0)
154 var cnt = parseInt($elem.data('count') || 0)
155 cnt +=1
155 cnt +=1
156 $elem.html(cnt);
156 $elem.html(cnt);
157 $elem.data('count', cnt);
157 $elem.data('count', cnt);
158 }
158 }
159 }
159 }
160
160
161 this.resetCounter = function () {
161 this.resetCounter = function () {
162 var $elem = $('#reviewers-cnt');
162 var $elem = $('#reviewers-cnt');
163
163
164 $elem.data('count', 0);
164 $elem.data('count', 0);
165 $elem.html(0);
165 $elem.html(0);
166
166
167 var $elem = $('#observers-cnt');
167 var $elem = $('#observers-cnt');
168
168
169 $elem.data('count', 0);
169 $elem.data('count', 0);
170 $elem.html(0);
170 $elem.html(0);
171 }
171 }
172
172
173 this.loadReviewRules = function (data) {
173 this.loadReviewRules = function (data) {
174 self.diffData = data;
174 self.diffData = data;
175
175
176 // reset forbidden Users
176 // reset forbidden Users
177 this.forbidUsers = self.defaultForbidUsers();
177 this.forbidUsers = self.defaultForbidUsers();
178
178
179 // reset state of review rules
179 // reset state of review rules
180 self.$rulesList.html('');
180 self.$rulesList.html('');
181
181
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
183 // default rule, case for older repo that don't have any rules stored
183 // default rule, case for older repo that don't have any rules stored
184 self.$rulesList.append(
184 self.$rulesList.append(
185 self.addRule(
185 self.addRule(
186 _gettext('All reviewers must vote.'))
186 _gettext('All reviewers must vote.'))
187 );
187 );
188 return self.forbidUsers
188 return self.forbidUsers
189 }
189 }
190
190
191 if (data.rules.voting !== undefined) {
191 if (data.rules.voting !== undefined) {
192 if (data.rules.voting < 0) {
192 if (data.rules.voting < 0) {
193 self.$rulesList.append(
193 self.$rulesList.append(
194 self.addRule(
194 self.addRule(
195 _gettext('All individual reviewers must vote.'))
195 _gettext('All individual reviewers must vote.'))
196 )
196 )
197 } else if (data.rules.voting === 1) {
197 } else if (data.rules.voting === 1) {
198 self.$rulesList.append(
198 self.$rulesList.append(
199 self.addRule(
199 self.addRule(
200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
201 )
201 )
202
202
203 } else {
203 } else {
204 self.$rulesList.append(
204 self.$rulesList.append(
205 self.addRule(
205 self.addRule(
206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
207 )
207 )
208 }
208 }
209 }
209 }
210
210
211 if (data.rules.voting_groups !== undefined) {
211 if (data.rules.voting_groups !== undefined) {
212 $.each(data.rules.voting_groups, function (index, rule_data) {
212 $.each(data.rules.voting_groups, function (index, rule_data) {
213 self.$rulesList.append(
213 self.$rulesList.append(
214 self.addRule(rule_data.text)
214 self.addRule(rule_data.text)
215 )
215 )
216 });
216 });
217 }
217 }
218
218
219 if (data.rules.use_code_authors_for_review) {
219 if (data.rules.use_code_authors_for_review) {
220 self.$rulesList.append(
220 self.$rulesList.append(
221 self.addRule(
221 self.addRule(
222 _gettext('Reviewers picked from source code changes.'))
222 _gettext('Reviewers picked from source code changes.'))
223 )
223 )
224 }
224 }
225
225
226 if (data.rules.forbid_adding_reviewers) {
226 if (data.rules.forbid_adding_reviewers) {
227 $('#add_reviewer_input').remove();
227 $('#add_reviewer_input').remove();
228 self.$rulesList.append(
228 self.$rulesList.append(
229 self.addRule(
229 self.addRule(
230 _gettext('Adding new reviewers is forbidden.'))
230 _gettext('Adding new reviewers is forbidden.'))
231 )
231 )
232 }
232 }
233
233
234 if (data.rules.forbid_author_to_review) {
234 if (data.rules.forbid_author_to_review) {
235 self.forbidUsers.push(data.rules_data.pr_author);
235 self.forbidUsers.push(data.rules_data.pr_author);
236 self.$rulesList.append(
236 self.$rulesList.append(
237 self.addRule(
237 self.addRule(
238 _gettext('Author is not allowed to be a reviewer.'))
238 _gettext('Author is not allowed to be a reviewer.'))
239 )
239 )
240 }
240 }
241
241
242 if (data.rules.forbid_commit_author_to_review) {
242 if (data.rules.forbid_commit_author_to_review) {
243
243
244 if (data.rules_data.forbidden_users) {
244 if (data.rules_data.forbidden_users) {
245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
246 self.forbidUsers.push(member_data)
246 self.forbidUsers.push(member_data)
247 });
247 });
248 }
248 }
249
249
250 self.$rulesList.append(
250 self.$rulesList.append(
251 self.addRule(
251 self.addRule(
252 _gettext('Commit Authors are not allowed to be a reviewer.'))
252 _gettext('Commit Authors are not allowed to be a reviewer.'))
253 )
253 )
254 }
254 }
255
255
256 // we don't have any rules set, so we inform users about it
256 // we don't have any rules set, so we inform users about it
257 if (self.enabledRules.length === 0) {
257 if (self.enabledRules.length === 0) {
258 self.addRule(
258 self.addRule(
259 _gettext('No review rules set.'))
259 _gettext('No review rules set.'))
260 }
260 }
261
261
262 return self.forbidUsers
262 return self.forbidUsers
263 };
263 };
264
264
265 this.emptyTables = function () {
265 this.emptyTables = function () {
266 self.emptyReviewersTable();
266 self.emptyReviewersTable();
267 self.emptyObserversTable();
267 self.emptyObserversTable();
268
268
269 // Also reset counters.
269 // Also reset counters.
270 self.resetCounter();
270 self.resetCounter();
271 }
271 }
272
272
273 this.emptyReviewersTable = function (withText) {
273 this.emptyReviewersTable = function (withText) {
274 self.$reviewMembers.empty();
274 self.$reviewMembers.empty();
275 if (withText !== undefined) {
275 if (withText !== undefined) {
276 self.$reviewMembers.html(withText)
276 self.$reviewMembers.html(withText)
277 }
277 }
278 };
278 };
279
279
280 this.emptyObserversTable = function (withText) {
280 this.emptyObserversTable = function (withText) {
281 self.$observerMembers.empty();
281 self.$observerMembers.empty();
282 if (withText !== undefined) {
282 if (withText !== undefined) {
283 self.$observerMembers.html(withText)
283 self.$observerMembers.html(withText)
284 }
284 }
285 }
285 }
286
286
287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
288
288
289 if (self.currentRequest) {
289 if (self.currentRequest) {
290 // make sure we cleanup old running requests before triggering this again
290 // make sure we cleanup old running requests before triggering this again
291 self.currentRequest.abort();
291 self.currentRequest.abort();
292 }
292 }
293
293
294 self.$loadingIndicator.show();
294 self.$loadingIndicator.show();
295
295
296 // reset reviewer/observe members
296 // reset reviewer/observe members
297 self.emptyTables();
297 self.emptyTables();
298
298
299 prButtonLock(true, null, 'reviewers');
299 prButtonLock(true, null, 'reviewers');
300 $('#user').hide(); // hide user autocomplete before load
300 $('#user').hide(); // hide user autocomplete before load
301 $('#observer').hide(); //hide observer autocomplete before load
301 $('#observer').hide(); //hide observer autocomplete before load
302
302
303 // lock PR button, so we cannot send PR before it's calculated
303 // lock PR button, so we cannot send PR before it's calculated
304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
305
305
306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
307 // don't load defaults in case we're missing some refs...
307 // don't load defaults in case we're missing some refs...
308 self.$loadingIndicator.hide();
308 self.$loadingIndicator.hide();
309 return
309 return
310 }
310 }
311
311
312 var url = pyroutes.url('repo_default_reviewers_data',
312 var url = pyroutes.url('repo_default_reviewers_data',
313 {
313 {
314 'repo_name': templateContext.repo_name,
314 'repo_name': templateContext.repo_name,
315 'source_repo': sourceRepo,
315 'source_repo': sourceRepo,
316 'source_ref': sourceRef[2],
316 'source_ref': sourceRef[2],
317 'target_repo': targetRepo,
317 'target_repo': targetRepo,
318 'target_ref': targetRef[2]
318 'target_ref': targetRef[2]
319 });
319 });
320
320
321 self.currentRequest = $.ajax({
321 self.currentRequest = $.ajax({
322 url: url,
322 url: url,
323 headers: {'X-PARTIAL-XHR': true},
323 headers: {'X-PARTIAL-XHR': true},
324 type: 'GET',
324 type: 'GET',
325 success: function (data) {
325 success: function (data) {
326
326
327 self.currentRequest = null;
327 self.currentRequest = null;
328
328
329 // review rules
329 // review rules
330 self.loadReviewRules(data);
330 self.loadReviewRules(data);
331 self.handleDiffData(data["diff_info"]);
331 self.handleDiffData(data["diff_info"]);
332
332
333 for (var i = 0; i < data.reviewers.length; i++) {
333 for (var i = 0; i < data.reviewers.length; i++) {
334 var reviewer = data.reviewers[i];
334 var reviewer = data.reviewers[i];
335 // load reviewer rules from the repo data
335 // load reviewer rules from the repo data
336 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
336 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
337 }
337 }
338
338
339
339
340 self.$loadingIndicator.hide();
340 self.$loadingIndicator.hide();
341 prButtonLock(false, null, 'reviewers');
341 prButtonLock(false, null, 'reviewers');
342
342
343 $('#user').show(); // show user autocomplete before load
343 $('#user').show(); // show user autocomplete before load
344 $('#observer').show(); // show observer autocomplete before load
344 $('#observer').show(); // show observer autocomplete before load
345
345
346 var commitElements = data["diff_info"]['commits'];
346 var commitElements = data["diff_info"]['commits'];
347
347
348 if (commitElements.length === 0) {
348 if (commitElements.length === 0) {
349 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
349 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
350 _gettext('There are no commits to merge.'));
350 _gettext('There are no commits to merge.'));
351 prButtonLock(true, noCommitsMsg, 'all');
351 prButtonLock(true, noCommitsMsg, 'all');
352
352
353 } else {
353 } else {
354 // un-lock PR button, so we cannot send PR before it's calculated
354 // un-lock PR button, so we cannot send PR before it's calculated
355 prButtonLock(false, null, 'compare');
355 prButtonLock(false, null, 'compare');
356 }
356 }
357
357
358 },
358 },
359 error: function (jqXHR, textStatus, errorThrown) {
359 error: function (jqXHR, textStatus, errorThrown) {
360 var prefix = "Loading diff and reviewers/observers failed\n"
360 var prefix = "Loading diff and reviewers/observers failed\n"
361 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
361 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
362 ajaxErrorSwal(message);
362 ajaxErrorSwal(message);
363 }
363 }
364 });
364 });
365
365
366 };
366 };
367
367
368 // check those, refactor
368 // check those, refactor
369 this.removeMember = function (reviewer_id, mark_delete) {
369 this.removeMember = function (reviewer_id, mark_delete) {
370 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
370 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
371
371
372 if (typeof (mark_delete) === undefined) {
372 if (typeof (mark_delete) === undefined) {
373 mark_delete = false;
373 mark_delete = false;
374 }
374 }
375
375
376 if (mark_delete === true) {
376 if (mark_delete === true) {
377 if (reviewer) {
377 if (reviewer) {
378 // now delete the input
378 // now delete the input
379 $('#reviewer_{0} input'.format(reviewer_id)).remove();
379 $('#reviewer_{0} input'.format(reviewer_id)).remove();
380 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
380 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
381 // mark as to-delete
381 // mark as to-delete
382 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
382 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
383 obj.addClass('to-delete');
383 obj.addClass('to-delete');
384 obj.css({"text-decoration": "line-through", "opacity": 0.5});
384 obj.css({"text-decoration": "line-through", "opacity": 0.5});
385 }
385 }
386 } else {
386 } else {
387 $('#reviewer_{0}'.format(reviewer_id)).remove();
387 $('#reviewer_{0}'.format(reviewer_id)).remove();
388 }
388 }
389 };
389 };
390
390
391 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
391 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
392
392
393 var id = reviewer_obj.user_id;
393 var id = reviewer_obj.user_id;
394 var username = reviewer_obj.username;
394 var username = reviewer_obj.username;
395
395
396 reasons = reasons || [];
396 reasons = reasons || [];
397 mandatory = mandatory || false;
397 mandatory = mandatory || false;
398 role = role || self.ROLE_REVIEWER
398 role = role || self.ROLE_REVIEWER
399
399
400 // register current set IDS to check if we don't have this ID already in
400 // register current set IDS to check if we don't have this ID already in
401 // and prevent duplicates
401 // and prevent duplicates
402 var currentIds = [];
402 var currentIds = [];
403
403
404 $.each($('.reviewer_entry'), function (index, value) {
404 $.each($('.reviewer_entry'), function (index, value) {
405 currentIds.push($(value).data('reviewerUserId'))
405 currentIds.push($(value).data('reviewerUserId'))
406 })
406 })
407
407
408 var userAllowedReview = function (userId) {
408 var userAllowedReview = function (userId) {
409 var allowed = true;
409 var allowed = true;
410 $.each(self.forbidUsers, function (index, member_data) {
410 $.each(self.forbidUsers, function (index, member_data) {
411 if (parseInt(userId) === member_data['user_id']) {
411 if (parseInt(userId) === member_data['user_id']) {
412 allowed = false;
412 allowed = false;
413 return false // breaks the loop
413 return false // breaks the loop
414 }
414 }
415 });
415 });
416 return allowed
416 return allowed
417 };
417 };
418
418
419 var userAllowed = userAllowedReview(id);
419 var userAllowed = userAllowedReview(id);
420
420
421 if (!userAllowed) {
421 if (!userAllowed) {
422 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
422 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
423 } else {
423 } else {
424 // only add if it's not there
424 // only add if it's not there
425 var alreadyReviewer = currentIds.indexOf(id) != -1;
425 var alreadyReviewer = currentIds.indexOf(id) != -1;
426
426
427 if (alreadyReviewer) {
427 if (alreadyReviewer) {
428 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
428 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
429 } else {
429 } else {
430
430
431 var reviewerEntry = renderTemplate('reviewMemberEntry', {
431 var reviewerEntry = renderTemplate('reviewMemberEntry', {
432 'member': reviewer_obj,
432 'member': reviewer_obj,
433 'mandatory': mandatory,
433 'mandatory': mandatory,
434 'role': role,
434 'role': role,
435 'reasons': reasons,
435 'reasons': reasons,
436 'allowed_to_update': true,
436 'allowed_to_update': true,
437 'review_status': 'not_reviewed',
437 'review_status': 'not_reviewed',
438 'review_status_label': _gettext('Not Reviewed'),
438 'review_status_label': _gettext('Not Reviewed'),
439 'user_group': reviewer_obj.user_group,
439 'user_group': reviewer_obj.user_group,
440 'create': true,
440 'create': true,
441 'rule_show': true,
441 'rule_show': true,
442 })
442 })
443
443
444 if (role === self.ROLE_REVIEWER) {
444 if (role === self.ROLE_REVIEWER) {
445 $(self.$reviewMembers.selector).append(reviewerEntry);
445 $(self.$reviewMembers.selector).append(reviewerEntry);
446 self.increaseCounter(self.ROLE_REVIEWER);
446 self.increaseCounter(self.ROLE_REVIEWER);
447 $('#reviewer-empty-msg').remove()
447 $('#reviewer-empty-msg').remove()
448 }
448 }
449 else if (role === self.ROLE_OBSERVER) {
449 else if (role === self.ROLE_OBSERVER) {
450 $(self.$observerMembers.selector).append(reviewerEntry);
450 $(self.$observerMembers.selector).append(reviewerEntry);
451 self.increaseCounter(self.ROLE_OBSERVER);
451 self.increaseCounter(self.ROLE_OBSERVER);
452 $('#observer-empty-msg').remove();
452 $('#observer-empty-msg').remove();
453 }
453 }
454
454
455 tooltipActivate();
455 tooltipActivate();
456 }
456 }
457 }
457 }
458
458
459 };
459 };
460
460
461 this.updateReviewers = function (repo_name, pull_request_id, role) {
461 this.updateReviewers = function (repo_name, pull_request_id, role) {
462 if (role === 'reviewer') {
462 if (role === 'reviewer') {
463 var postData = $('#reviewers input').serialize();
463 var postData = $('#reviewers input').serialize();
464 _updatePullRequest(repo_name, pull_request_id, postData);
464 _updatePullRequest(repo_name, pull_request_id, postData);
465 } else if (role === 'observer') {
465 } else if (role === 'observer') {
466 var postData = $('#observers input').serialize();
466 var postData = $('#observers input').serialize();
467 _updatePullRequest(repo_name, pull_request_id, postData);
467 _updatePullRequest(repo_name, pull_request_id, postData);
468 }
468 }
469 };
469 };
470
470
471 this.handleDiffData = function (data) {
471 this.handleDiffData = function (data) {
472 self.diffDataHandler(data)
472 self.diffDataHandler(data)
473 }
473 }
474 };
474 };
475
475
476
476
477 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
477 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
478 var url = pyroutes.url(
478 var url = pyroutes.url(
479 'pullrequest_update',
479 'pullrequest_update',
480 {"repo_name": repo_name, "pull_request_id": pull_request_id});
480 {"repo_name": repo_name, "pull_request_id": pull_request_id});
481 if (typeof postData === 'string' ) {
481 if (typeof postData === 'string' ) {
482 postData += '&csrf_token=' + CSRF_TOKEN;
482 postData += '&csrf_token=' + CSRF_TOKEN;
483 } else {
483 } else {
484 postData.csrf_token = CSRF_TOKEN;
484 postData.csrf_token = CSRF_TOKEN;
485 }
485 }
486
486
487 var success = function(o) {
487 var success = function(o) {
488 var redirectUrl = o['redirect_url'];
488 var redirectUrl = o['redirect_url'];
489 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
489 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
490 window.location = redirectUrl;
490 window.location = redirectUrl;
491 } else {
491 } else {
492 window.location.reload();
492 window.location.reload();
493 }
493 }
494 };
494 };
495
495
496 ajaxPOST(url, postData, success);
496 ajaxPOST(url, postData, success);
497 };
497 };
498
498
499 /**
499 /**
500 * PULL REQUEST update commits
500 * PULL REQUEST update commits
501 */
501 */
502 var updateCommits = function(repo_name, pull_request_id, force) {
502 var updateCommits = function(repo_name, pull_request_id, force) {
503 var postData = {
503 var postData = {
504 'update_commits': true
504 'update_commits': true
505 };
505 };
506 if (force !== undefined && force === true) {
506 if (force !== undefined && force === true) {
507 postData['force_refresh'] = true
507 postData['force_refresh'] = true
508 }
508 }
509 _updatePullRequest(repo_name, pull_request_id, postData);
509 _updatePullRequest(repo_name, pull_request_id, postData);
510 };
510 };
511
511
512
512
513 /**
513 /**
514 * PULL REQUEST edit info
514 * PULL REQUEST edit info
515 */
515 */
516 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
516 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
517 var url = pyroutes.url(
517 var url = pyroutes.url(
518 'pullrequest_update',
518 'pullrequest_update',
519 {"repo_name": repo_name, "pull_request_id": pull_request_id});
519 {"repo_name": repo_name, "pull_request_id": pull_request_id});
520
520
521 var postData = {
521 var postData = {
522 'title': title,
522 'title': title,
523 'description': description,
523 'description': description,
524 'description_renderer': renderer,
524 'description_renderer': renderer,
525 'edit_pull_request': true,
525 'edit_pull_request': true,
526 'csrf_token': CSRF_TOKEN
526 'csrf_token': CSRF_TOKEN
527 };
527 };
528 var success = function(o) {
528 var success = function(o) {
529 window.location.reload();
529 window.location.reload();
530 };
530 };
531 ajaxPOST(url, postData, success);
531 ajaxPOST(url, postData, success);
532 };
532 };
533
533
534
534
535 /**
535 /**
536 * autocomplete handler for reviewers/observers
536 * autocomplete handler for reviewers/observers
537 */
537 */
538 var autoCompleteHandler = function (inputId, controller, role) {
538 var autoCompleteHandler = function (inputId, controller, role) {
539
539
540 return function (element, data) {
540 return function (element, data) {
541 var mandatory = false;
541 var mandatory = false;
542 var reasons = [_gettext('added manually by "{0}"').format(
542 var reasons = [_gettext('added manually by "{0}"').format(
543 templateContext.rhodecode_user.username)];
543 templateContext.rhodecode_user.username)];
544
544
545 // add whole user groups
545 // add whole user groups
546 if (data.value_type == 'user_group') {
546 if (data.value_type == 'user_group') {
547 reasons.push(_gettext('member of "{0}"').format(data.value_display));
547 reasons.push(_gettext('member of "{0}"').format(data.value_display));
548
548
549 $.each(data.members, function (index, member_data) {
549 $.each(data.members, function (index, member_data) {
550 var reviewer = member_data;
550 var reviewer = member_data;
551 reviewer['user_id'] = member_data['id'];
551 reviewer['user_id'] = member_data['id'];
552 reviewer['gravatar_link'] = member_data['icon_link'];
552 reviewer['gravatar_link'] = member_data['icon_link'];
553 reviewer['user_link'] = member_data['profile_link'];
553 reviewer['user_link'] = member_data['profile_link'];
554 reviewer['rules'] = [];
554 reviewer['rules'] = [];
555 controller.addMember(reviewer, reasons, mandatory, role);
555 controller.addMember(reviewer, reasons, mandatory, role);
556 })
556 })
557 }
557 }
558 // add single user
558 // add single user
559 else {
559 else {
560 var reviewer = data;
560 var reviewer = data;
561 reviewer['user_id'] = data['id'];
561 reviewer['user_id'] = data['id'];
562 reviewer['gravatar_link'] = data['icon_link'];
562 reviewer['gravatar_link'] = data['icon_link'];
563 reviewer['user_link'] = data['profile_link'];
563 reviewer['user_link'] = data['profile_link'];
564 reviewer['rules'] = [];
564 reviewer['rules'] = [];
565 controller.addMember(reviewer, reasons, mandatory, role);
565 controller.addMember(reviewer, reasons, mandatory, role);
566 }
566 }
567
567
568 $(inputId).val('');
568 $(inputId).val('');
569 }
569 }
570 }
570 }
571
571
572 /**
572 /**
573 * Reviewer autocomplete
573 * Reviewer autocomplete
574 */
574 */
575 var ReviewerAutoComplete = function (inputId, controller) {
575 var ReviewerAutoComplete = function (inputId, controller) {
576 var self = this;
576 var self = this;
577 self.controller = controller;
577 self.controller = controller;
578 self.inputId = inputId;
578 self.inputId = inputId;
579 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
579 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
580
580
581 $(inputId).autocomplete({
581 $(inputId).autocomplete({
582 serviceUrl: pyroutes.url('user_autocomplete_data'),
582 serviceUrl: pyroutes.url('user_autocomplete_data'),
583 minChars: 2,
583 minChars: 2,
584 maxHeight: 400,
584 maxHeight: 400,
585 deferRequestBy: 300, //miliseconds
585 deferRequestBy: 300, //miliseconds
586 showNoSuggestionNotice: true,
586 showNoSuggestionNotice: true,
587 tabDisabled: true,
587 tabDisabled: true,
588 autoSelectFirst: true,
588 autoSelectFirst: true,
589 params: {
589 params: {
590 user_id: templateContext.rhodecode_user.user_id,
590 user_id: templateContext.rhodecode_user.user_id,
591 user_groups: true,
591 user_groups: true,
592 user_groups_expand: true,
592 user_groups_expand: true,
593 skip_default_user: true
593 skip_default_user: true
594 },
594 },
595 formatResult: autocompleteFormatResult,
595 formatResult: autocompleteFormatResult,
596 lookupFilter: autocompleteFilterResult,
596 lookupFilter: autocompleteFilterResult,
597 onSelect: handler
597 onSelect: handler
598 });
598 });
599 };
599 };
600
600
601 /**
601 /**
602 * Observers autocomplete
602 * Observers autocomplete
603 */
603 */
604 var ObserverAutoComplete = function(inputId, controller) {
604 var ObserverAutoComplete = function(inputId, controller) {
605 var self = this;
605 var self = this;
606 self.controller = controller;
606 self.controller = controller;
607 self.inputId = inputId;
607 self.inputId = inputId;
608 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
608 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
609
609
610 $(inputId).autocomplete({
610 $(inputId).autocomplete({
611 serviceUrl: pyroutes.url('user_autocomplete_data'),
611 serviceUrl: pyroutes.url('user_autocomplete_data'),
612 minChars: 2,
612 minChars: 2,
613 maxHeight: 400,
613 maxHeight: 400,
614 deferRequestBy: 300, //miliseconds
614 deferRequestBy: 300, //miliseconds
615 showNoSuggestionNotice: true,
615 showNoSuggestionNotice: true,
616 tabDisabled: true,
616 tabDisabled: true,
617 autoSelectFirst: true,
617 autoSelectFirst: true,
618 params: {
618 params: {
619 user_id: templateContext.rhodecode_user.user_id,
619 user_id: templateContext.rhodecode_user.user_id,
620 user_groups: true,
620 user_groups: true,
621 user_groups_expand: true,
621 user_groups_expand: true,
622 skip_default_user: true
622 skip_default_user: true
623 },
623 },
624 formatResult: autocompleteFormatResult,
624 formatResult: autocompleteFormatResult,
625 lookupFilter: autocompleteFilterResult,
625 lookupFilter: autocompleteFilterResult,
626 onSelect: handler
626 onSelect: handler
627 });
627 });
628 }
628 }
629
629
630
630
631 window.VersionController = function () {
631 window.VersionController = function () {
632 var self = this;
632 var self = this;
633 this.$verSource = $('input[name=ver_source]');
633 this.$verSource = $('input[name=ver_source]');
634 this.$verTarget = $('input[name=ver_target]');
634 this.$verTarget = $('input[name=ver_target]');
635 this.$showVersionDiff = $('#show-version-diff');
635 this.$showVersionDiff = $('#show-version-diff');
636
636
637 this.adjustRadioSelectors = function (curNode) {
637 this.adjustRadioSelectors = function (curNode) {
638 var getVal = function (item) {
638 var getVal = function (item) {
639 if (item === 'latest') {
639 if (item === 'latest') {
640 return Number.MAX_SAFE_INTEGER
640 return Number.MAX_SAFE_INTEGER
641 }
641 }
642 else {
642 else {
643 return parseInt(item)
643 return parseInt(item)
644 }
644 }
645 };
645 };
646
646
647 var curVal = getVal($(curNode).val());
647 var curVal = getVal($(curNode).val());
648 var cleared = false;
648 var cleared = false;
649
649
650 $.each(self.$verSource, function (index, value) {
650 $.each(self.$verSource, function (index, value) {
651 var elVal = getVal($(value).val());
651 var elVal = getVal($(value).val());
652
652
653 if (elVal > curVal) {
653 if (elVal > curVal) {
654 if ($(value).is(':checked')) {
654 if ($(value).is(':checked')) {
655 cleared = true;
655 cleared = true;
656 }
656 }
657 $(value).attr('disabled', 'disabled');
657 $(value).attr('disabled', 'disabled');
658 $(value).removeAttr('checked');
658 $(value).removeAttr('checked');
659 $(value).css({'opacity': 0.1});
659 $(value).css({'opacity': 0.1});
660 }
660 }
661 else {
661 else {
662 $(value).css({'opacity': 1});
662 $(value).css({'opacity': 1});
663 $(value).removeAttr('disabled');
663 $(value).removeAttr('disabled');
664 }
664 }
665 });
665 });
666
666
667 if (cleared) {
667 if (cleared) {
668 // if we unchecked an active, set the next one to same loc.
668 // if we unchecked an active, set the next one to same loc.
669 $(this.$verSource).filter('[value={0}]'.format(
669 $(this.$verSource).filter('[value={0}]'.format(
670 curVal)).attr('checked', 'checked');
670 curVal)).attr('checked', 'checked');
671 }
671 }
672
672
673 self.setLockAction(false,
673 self.setLockAction(false,
674 $(curNode).data('verPos'),
674 $(curNode).data('verPos'),
675 $(this.$verSource).filter(':checked').data('verPos')
675 $(this.$verSource).filter(':checked').data('verPos')
676 );
676 );
677 };
677 };
678
678
679
679
680 this.attachVersionListener = function () {
680 this.attachVersionListener = function () {
681 self.$verTarget.change(function (e) {
681 self.$verTarget.change(function (e) {
682 self.adjustRadioSelectors(this)
682 self.adjustRadioSelectors(this)
683 });
683 });
684 self.$verSource.change(function (e) {
684 self.$verSource.change(function (e) {
685 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
685 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
686 });
686 });
687 };
687 };
688
688
689 this.init = function () {
689 this.init = function () {
690
690
691 var curNode = self.$verTarget.filter(':checked');
691 var curNode = self.$verTarget.filter(':checked');
692 self.adjustRadioSelectors(curNode);
692 self.adjustRadioSelectors(curNode);
693 self.setLockAction(true);
693 self.setLockAction(true);
694 self.attachVersionListener();
694 self.attachVersionListener();
695
695
696 };
696 };
697
697
698 this.setLockAction = function (state, selectedVersion, otherVersion) {
698 this.setLockAction = function (state, selectedVersion, otherVersion) {
699 var $showVersionDiff = this.$showVersionDiff;
699 var $showVersionDiff = this.$showVersionDiff;
700
700
701 if (state) {
701 if (state) {
702 $showVersionDiff.attr('disabled', 'disabled');
702 $showVersionDiff.attr('disabled', 'disabled');
703 $showVersionDiff.addClass('disabled');
703 $showVersionDiff.addClass('disabled');
704 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
704 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
705 }
705 }
706 else {
706 else {
707 $showVersionDiff.removeAttr('disabled');
707 $showVersionDiff.removeAttr('disabled');
708 $showVersionDiff.removeClass('disabled');
708 $showVersionDiff.removeClass('disabled');
709
709
710 if (selectedVersion == otherVersion) {
710 if (selectedVersion == otherVersion) {
711 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
711 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
712 } else {
712 } else {
713 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
713 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
714 }
714 }
715 }
715 }
716
716
717 };
717 };
718
718
719 this.showVersionDiff = function () {
719 this.showVersionDiff = function () {
720 var target = self.$verTarget.filter(':checked');
720 var target = self.$verTarget.filter(':checked');
721 var source = self.$verSource.filter(':checked');
721 var source = self.$verSource.filter(':checked');
722
722
723 if (target.val() && source.val()) {
723 if (target.val() && source.val()) {
724 var params = {
724 var params = {
725 'pull_request_id': templateContext.pull_request_data.pull_request_id,
725 'pull_request_id': templateContext.pull_request_data.pull_request_id,
726 'repo_name': templateContext.repo_name,
726 'repo_name': templateContext.repo_name,
727 'version': target.val(),
727 'version': target.val(),
728 'from_version': source.val()
728 'from_version': source.val()
729 };
729 };
730 window.location = pyroutes.url('pullrequest_show', params)
730 window.location = pyroutes.url('pullrequest_show', params)
731 }
731 }
732
732
733 return false;
733 return false;
734 };
734 };
735
735
736 this.toggleVersionView = function (elem) {
736 this.toggleVersionView = function (elem) {
737
737
738 if (this.$showVersionDiff.is(':visible')) {
738 if (this.$showVersionDiff.is(':visible')) {
739 $('.version-pr').hide();
739 $('.version-pr').hide();
740 this.$showVersionDiff.hide();
740 this.$showVersionDiff.hide();
741 $(elem).html($(elem).data('toggleOn'))
741 $(elem).html($(elem).data('toggleOn'))
742 } else {
742 } else {
743 $('.version-pr').show();
743 $('.version-pr').show();
744 this.$showVersionDiff.show();
744 this.$showVersionDiff.show();
745 $(elem).html($(elem).data('toggleOff'))
745 $(elem).html($(elem).data('toggleOff'))
746 }
746 }
747
747
748 return false
748 return false
749 };
749 };
750
750
751 };
751 };
752
752
753
753
754 window.UpdatePrController = function () {
754 window.UpdatePrController = function () {
755 var self = this;
755 var self = this;
756 this.$updateCommits = $('#update_commits');
756 this.$updateCommits = $('#update_commits');
757 this.$updateCommitsSwitcher = $('#update_commits_switcher');
757 this.$updateCommitsSwitcher = $('#update_commits_switcher');
758
758
759 this.lockUpdateButton = function (label) {
759 this.lockUpdateButton = function (label) {
760 self.$updateCommits.attr('disabled', 'disabled');
760 self.$updateCommits.attr('disabled', 'disabled');
761 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
761 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
762
762
763 self.$updateCommits.addClass('disabled');
763 self.$updateCommits.addClass('disabled');
764 self.$updateCommitsSwitcher.addClass('disabled');
764 self.$updateCommitsSwitcher.addClass('disabled');
765
765
766 self.$updateCommits.removeClass('btn-primary');
766 self.$updateCommits.removeClass('btn-primary');
767 self.$updateCommitsSwitcher.removeClass('btn-primary');
767 self.$updateCommitsSwitcher.removeClass('btn-primary');
768
768
769 self.$updateCommits.text(_gettext(label));
769 self.$updateCommits.text(_gettext(label));
770 };
770 };
771
771
772 this.isUpdateLocked = function () {
772 this.isUpdateLocked = function () {
773 return self.$updateCommits.attr('disabled') !== undefined;
773 return self.$updateCommits.attr('disabled') !== undefined;
774 };
774 };
775
775
776 this.updateCommits = function (curNode) {
776 this.updateCommits = function (curNode) {
777 if (self.isUpdateLocked()) {
777 if (self.isUpdateLocked()) {
778 return
778 return
779 }
779 }
780 self.lockUpdateButton(_gettext('Updating...'));
780 self.lockUpdateButton(_gettext('Updating...'));
781 updateCommits(
781 updateCommits(
782 templateContext.repo_name,
782 templateContext.repo_name,
783 templateContext.pull_request_data.pull_request_id);
783 templateContext.pull_request_data.pull_request_id);
784 };
784 };
785
785
786 this.forceUpdateCommits = function () {
786 this.forceUpdateCommits = function () {
787 if (self.isUpdateLocked()) {
787 if (self.isUpdateLocked()) {
788 return
788 return
789 }
789 }
790 self.lockUpdateButton(_gettext('Force updating...'));
790 self.lockUpdateButton(_gettext('Force updating...'));
791 var force = true;
791 var force = true;
792 updateCommits(
792 updateCommits(
793 templateContext.repo_name,
793 templateContext.repo_name,
794 templateContext.pull_request_data.pull_request_id, force);
794 templateContext.pull_request_data.pull_request_id, force);
795 };
795 };
796 };
796 };
797
797
798
798
799 /**
799 /**
800 * Reviewer display panel
800 * Reviewer display panel
801 */
801 */
802 window.ReviewersPanel = {
802 window.ReviewersPanel = {
803 editButton: null,
803 editButton: null,
804 closeButton: null,
804 closeButton: null,
805 addButton: null,
805 addButton: null,
806 removeButtons: null,
806 removeButtons: null,
807 reviewRules: null,
807 reviewRules: null,
808 setReviewers: null,
808 setReviewers: null,
809 controller: null,
809
810
810 setSelectors: function () {
811 setSelectors: function () {
811 var self = this;
812 var self = this;
812 self.editButton = $('#open_edit_reviewers');
813 self.editButton = $('#open_edit_reviewers');
813 self.closeButton =$('#close_edit_reviewers');
814 self.closeButton =$('#close_edit_reviewers');
814 self.addButton = $('#add_reviewer');
815 self.addButton = $('#add_reviewer');
815 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
816 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
816 },
817 },
817
818
818 init: function (reviewRules, setReviewers) {
819 init: function (controller, reviewRules, setReviewers) {
819 var self = this;
820 var self = this;
820 self.setSelectors();
821 self.setSelectors();
821
822
822 this.reviewRules = reviewRules;
823 self.controller = controller;
823 this.setReviewers = setReviewers;
824 self.reviewRules = reviewRules;
825 self.setReviewers = setReviewers;
824
826
825 this.editButton.on('click', function (e) {
827 self.editButton.on('click', function (e) {
826 self.edit();
828 self.edit();
827 });
829 });
828 this.closeButton.on('click', function (e) {
830 self.closeButton.on('click', function (e) {
829 self.close();
831 self.close();
830 self.renderReviewers();
832 self.renderReviewers();
831 });
833 });
832
834
833 self.renderReviewers();
835 self.renderReviewers();
834
836
835 },
837 },
836
838
837 renderReviewers: function () {
839 renderReviewers: function () {
838 if (this.setReviewers.reviewers === undefined) {
840 var self = this;
841
842 if (self.setReviewers.reviewers === undefined) {
839 return
843 return
840 }
844 }
841 if (this.setReviewers.reviewers.length === 0) {
845 if (self.setReviewers.reviewers.length === 0) {
842 reviewersController.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
846 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
843 return
847 return
844 }
848 }
845
849
846 reviewersController.emptyReviewersTable();
850 self.controller.emptyReviewersTable();
847
851
848 $.each(this.setReviewers.reviewers, function (key, val) {
852 $.each(self.setReviewers.reviewers, function (key, val) {
849
853
850 var member = val;
854 var member = val;
851 if (member.role === reviewersController.ROLE_REVIEWER) {
855 if (member.role === self.controller.ROLE_REVIEWER) {
852 var entry = renderTemplate('reviewMemberEntry', {
856 var entry = renderTemplate('reviewMemberEntry', {
853 'member': member,
857 'member': member,
854 'mandatory': member.mandatory,
858 'mandatory': member.mandatory,
855 'role': member.role,
859 'role': member.role,
856 'reasons': member.reasons,
860 'reasons': member.reasons,
857 'allowed_to_update': member.allowed_to_update,
861 'allowed_to_update': member.allowed_to_update,
858 'review_status': member.review_status,
862 'review_status': member.review_status,
859 'review_status_label': member.review_status_label,
863 'review_status_label': member.review_status_label,
860 'user_group': member.user_group,
864 'user_group': member.user_group,
861 'create': false
865 'create': false
862 });
866 });
863
867
864 $(reviewersController.$reviewMembers.selector).append(entry)
868 $(self.controller.$reviewMembers.selector).append(entry)
865 }
869 }
866 });
870 });
867
871
868 tooltipActivate();
872 tooltipActivate();
869 },
873 },
870
874
871 edit: function (event) {
875 edit: function (event) {
872 this.editButton.hide();
876 var self = this;
873 this.closeButton.show();
877 self.editButton.hide();
874 this.addButton.show();
878 self.closeButton.show();
875 $(this.removeButtons.selector).css('visibility', 'visible');
879 self.addButton.show();
880 $(self.removeButtons.selector).css('visibility', 'visible');
876 // review rules
881 // review rules
877 reviewersController.loadReviewRules(this.reviewRules);
882 self.controller.loadReviewRules(this.reviewRules);
878 },
883 },
879
884
880 close: function (event) {
885 close: function (event) {
886 var self = this;
881 this.editButton.show();
887 this.editButton.show();
882 this.closeButton.hide();
888 this.closeButton.hide();
883 this.addButton.hide();
889 this.addButton.hide();
884 $(this.removeButtons.selector).css('visibility', 'hidden');
890 $(this.removeButtons.selector).css('visibility', 'hidden');
885 // hide review rules
891 // hide review rules
886 reviewersController.hideReviewRules();
892 self.controller.hideReviewRules();
887 }
893 }
888 };
894 };
889
895
890 /**
896 /**
891 * Reviewer display panel
897 * Reviewer display panel
892 */
898 */
893 window.ObserversPanel = {
899 window.ObserversPanel = {
894 editButton: null,
900 editButton: null,
895 closeButton: null,
901 closeButton: null,
896 addButton: null,
902 addButton: null,
897 removeButtons: null,
903 removeButtons: null,
898 reviewRules: null,
904 reviewRules: null,
899 setReviewers: null,
905 setReviewers: null,
906 controller: null,
900
907
901 setSelectors: function () {
908 setSelectors: function () {
902 var self = this;
909 var self = this;
903 self.editButton = $('#open_edit_observers');
910 self.editButton = $('#open_edit_observers');
904 self.closeButton =$('#close_edit_observers');
911 self.closeButton =$('#close_edit_observers');
905 self.addButton = $('#add_observer');
912 self.addButton = $('#add_observer');
906 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
913 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
907 },
914 },
908
915
909 init: function (reviewRules, setReviewers) {
916 init: function (controller, reviewRules, setReviewers) {
910 var self = this;
917 var self = this;
911 self.setSelectors();
918 self.setSelectors();
912
919
913 this.reviewRules = reviewRules;
920 self.controller = controller;
914 this.setReviewers = setReviewers;
921 self.reviewRules = reviewRules;
922 self.setReviewers = setReviewers;
915
923
916 this.editButton.on('click', function (e) {
924 self.editButton.on('click', function (e) {
917 self.edit();
925 self.edit();
918 });
926 });
919 this.closeButton.on('click', function (e) {
927 self.closeButton.on('click', function (e) {
920 self.close();
928 self.close();
921 self.renderObservers();
929 self.renderObservers();
922 });
930 });
923
931
924 self.renderObservers();
932 self.renderObservers();
925
933
926 },
934 },
927
935
928 renderObservers: function () {
936 renderObservers: function () {
929 if (this.setReviewers.observers === undefined) {
937 var self = this;
938 if (self.setReviewers.observers === undefined) {
930 return
939 return
931 }
940 }
932 if (this.setReviewers.observers.length === 0) {
941 if (self.setReviewers.observers.length === 0) {
933 reviewersController.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
942 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
934 return
943 return
935 }
944 }
936
945
937 reviewersController.emptyObserversTable();
946 self.controller.emptyObserversTable();
938
947
939 $.each(this.setReviewers.observers, function (key, val) {
948 $.each(self.setReviewers.observers, function (key, val) {
940 var member = val;
949 var member = val;
941 if (member.role === reviewersController.ROLE_OBSERVER) {
950 if (member.role === self.controller.ROLE_OBSERVER) {
942 var entry = renderTemplate('reviewMemberEntry', {
951 var entry = renderTemplate('reviewMemberEntry', {
943 'member': member,
952 'member': member,
944 'mandatory': member.mandatory,
953 'mandatory': member.mandatory,
945 'role': member.role,
954 'role': member.role,
946 'reasons': member.reasons,
955 'reasons': member.reasons,
947 'allowed_to_update': member.allowed_to_update,
956 'allowed_to_update': member.allowed_to_update,
948 'review_status': member.review_status,
957 'review_status': member.review_status,
949 'review_status_label': member.review_status_label,
958 'review_status_label': member.review_status_label,
950 'user_group': member.user_group,
959 'user_group': member.user_group,
951 'create': false
960 'create': false
952 });
961 });
953
962
954 $(reviewersController.$observerMembers.selector).append(entry)
963 $(self.controller.$observerMembers.selector).append(entry)
955 }
964 }
956 });
965 });
957
966
958 tooltipActivate();
967 tooltipActivate();
959 },
968 },
960
969
961 edit: function (event) {
970 edit: function (event) {
962 this.editButton.hide();
971 this.editButton.hide();
963 this.closeButton.show();
972 this.closeButton.show();
964 this.addButton.show();
973 this.addButton.show();
965 $(this.removeButtons.selector).css('visibility', 'visible');
974 $(this.removeButtons.selector).css('visibility', 'visible');
966 },
975 },
967
976
968 close: function (event) {
977 close: function (event) {
969 this.editButton.show();
978 this.editButton.show();
970 this.closeButton.hide();
979 this.closeButton.hide();
971 this.addButton.hide();
980 this.addButton.hide();
972 $(this.removeButtons.selector).css('visibility', 'hidden');
981 $(this.removeButtons.selector).css('visibility', 'hidden');
973 }
982 }
974
983
975 };
984 };
976
985
977 window.PRDetails = {
986 window.PRDetails = {
978 editButton: null,
987 editButton: null,
979 closeButton: null,
988 closeButton: null,
980 deleteButton: null,
989 deleteButton: null,
981 viewFields: null,
990 viewFields: null,
982 editFields: null,
991 editFields: null,
983
992
984 setSelectors: function () {
993 setSelectors: function () {
985 var self = this;
994 var self = this;
986 self.editButton = $('#open_edit_pullrequest')
995 self.editButton = $('#open_edit_pullrequest')
987 self.closeButton = $('#close_edit_pullrequest')
996 self.closeButton = $('#close_edit_pullrequest')
988 self.deleteButton = $('#delete_pullrequest')
997 self.deleteButton = $('#delete_pullrequest')
989 self.viewFields = $('#pr-desc, #pr-title')
998 self.viewFields = $('#pr-desc, #pr-title')
990 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
999 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
991 },
1000 },
992
1001
993 init: function () {
1002 init: function () {
994 var self = this;
1003 var self = this;
995 self.setSelectors();
1004 self.setSelectors();
996 self.editButton.on('click', function (e) {
1005 self.editButton.on('click', function (e) {
997 self.edit();
1006 self.edit();
998 });
1007 });
999 self.closeButton.on('click', function (e) {
1008 self.closeButton.on('click', function (e) {
1000 self.view();
1009 self.view();
1001 });
1010 });
1002 },
1011 },
1003
1012
1004 edit: function (event) {
1013 edit: function (event) {
1005 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1014 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1006 this.viewFields.hide();
1015 this.viewFields.hide();
1007 this.editButton.hide();
1016 this.editButton.hide();
1008 this.deleteButton.hide();
1017 this.deleteButton.hide();
1009 this.closeButton.show();
1018 this.closeButton.show();
1010 this.editFields.show();
1019 this.editFields.show();
1011 cmInstance.refresh();
1020 cmInstance.refresh();
1012 },
1021 },
1013
1022
1014 view: function (event) {
1023 view: function (event) {
1015 this.editButton.show();
1024 this.editButton.show();
1016 this.deleteButton.show();
1025 this.deleteButton.show();
1017 this.editFields.hide();
1026 this.editFields.hide();
1018 this.closeButton.hide();
1027 this.closeButton.hide();
1019 this.viewFields.show();
1028 this.viewFields.show();
1020 }
1029 }
1021 };
1030 };
1022
1031
1023 /**
1032 /**
1024 * OnLine presence using channelstream
1033 * OnLine presence using channelstream
1025 */
1034 */
1026 window.ReviewerPresenceController = function (channel) {
1035 window.ReviewerPresenceController = function (channel) {
1027 var self = this;
1036 var self = this;
1028 this.channel = channel;
1037 this.channel = channel;
1029 this.users = {};
1038 this.users = {};
1030
1039
1031 this.storeUsers = function (users) {
1040 this.storeUsers = function (users) {
1032 self.users = {}
1041 self.users = {}
1033 $.each(users, function (index, value) {
1042 $.each(users, function (index, value) {
1034 var userId = value.state.id;
1043 var userId = value.state.id;
1035 self.users[userId] = value.state;
1044 self.users[userId] = value.state;
1036 })
1045 })
1037 }
1046 }
1038
1047
1039 this.render = function () {
1048 this.render = function () {
1040 $.each($('.reviewer_entry'), function (index, value) {
1049 $.each($('.reviewer_entry'), function (index, value) {
1041 var userData = $(value).data();
1050 var userData = $(value).data();
1042 if (self.users[userData.reviewerUserId] !== undefined) {
1051 if (self.users[userData.reviewerUserId] !== undefined) {
1043 $(value).find('.presence-state').show();
1052 $(value).find('.presence-state').show();
1044 } else {
1053 } else {
1045 $(value).find('.presence-state').hide();
1054 $(value).find('.presence-state').hide();
1046 }
1055 }
1047 })
1056 })
1048 };
1057 };
1049
1058
1050 this.handlePresence = function (data) {
1059 this.handlePresence = function (data) {
1051 if (data.type == 'presence' && data.channel === self.channel) {
1060 if (data.type == 'presence' && data.channel === self.channel) {
1052 this.storeUsers(data.users);
1061 this.storeUsers(data.users);
1053 this.render()
1062 this.render()
1054 }
1063 }
1055 };
1064 };
1056
1065
1057 this.handleChannelUpdate = function (data) {
1066 this.handleChannelUpdate = function (data) {
1058 if (data.channel === this.channel) {
1067 if (data.channel === this.channel) {
1059 this.storeUsers(data.state.users);
1068 this.storeUsers(data.state.users);
1060 this.render()
1069 this.render()
1061 }
1070 }
1062
1071
1063 };
1072 };
1064
1073
1065 /* subscribe to the current presence */
1074 /* subscribe to the current presence */
1066 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1075 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1067 /* subscribe to updates e.g connect/disconnect */
1076 /* subscribe to updates e.g connect/disconnect */
1068 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1077 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1069
1078
1070 };
1079 };
1071
1080
1072 window.refreshComments = function (version) {
1081 window.refreshComments = function (version) {
1073 version = version || templateContext.pull_request_data.pull_request_version || '';
1082 version = version || templateContext.pull_request_data.pull_request_version || '';
1074
1083
1075 // Pull request case
1084 // Pull request case
1076 if (templateContext.pull_request_data.pull_request_id !== null) {
1085 if (templateContext.pull_request_data.pull_request_id !== null) {
1077 var params = {
1086 var params = {
1078 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1087 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1079 'repo_name': templateContext.repo_name,
1088 'repo_name': templateContext.repo_name,
1080 'version': version,
1089 'version': version,
1081 };
1090 };
1082 var loadUrl = pyroutes.url('pullrequest_comments', params);
1091 var loadUrl = pyroutes.url('pullrequest_comments', params);
1083 } // commit case
1092 } // commit case
1084 else {
1093 else {
1085 return
1094 return
1086 }
1095 }
1087
1096
1088 var currentIDs = []
1097 var currentIDs = []
1089 $.each($('.comment'), function (idx, element) {
1098 $.each($('.comment'), function (idx, element) {
1090 currentIDs.push($(element).data('commentId'));
1099 currentIDs.push($(element).data('commentId'));
1091 });
1100 });
1092 var data = {"comments": currentIDs};
1101 var data = {"comments": currentIDs};
1093
1102
1094 var $targetElem = $('.comments-content-table');
1103 var $targetElem = $('.comments-content-table');
1095 $targetElem.css('opacity', 0.3);
1104 $targetElem.css('opacity', 0.3);
1096
1105
1097 var success = function (data) {
1106 var success = function (data) {
1098 var $counterElem = $('#comments-count');
1107 var $counterElem = $('#comments-count');
1099 var newCount = $(data).data('counter');
1108 var newCount = $(data).data('counter');
1100 if (newCount !== undefined) {
1109 if (newCount !== undefined) {
1101 var callback = function () {
1110 var callback = function () {
1102 $counterElem.animate({'opacity': 1.00}, 200)
1111 $counterElem.animate({'opacity': 1.00}, 200)
1103 $counterElem.html(newCount);
1112 $counterElem.html(newCount);
1104 };
1113 };
1105 $counterElem.animate({'opacity': 0.15}, 200, callback);
1114 $counterElem.animate({'opacity': 0.15}, 200, callback);
1106 }
1115 }
1107
1116
1108 $targetElem.css('opacity', 1);
1117 $targetElem.css('opacity', 1);
1109 $targetElem.html(data);
1118 $targetElem.html(data);
1110 tooltipActivate();
1119 tooltipActivate();
1111 }
1120 }
1112
1121
1113 ajaxPOST(loadUrl, data, success, null, {})
1122 ajaxPOST(loadUrl, data, success, null, {})
1114
1123
1115 }
1124 }
1116
1125
1117 window.refreshTODOs = function (version) {
1126 window.refreshTODOs = function (version) {
1118 version = version || templateContext.pull_request_data.pull_request_version || '';
1127 version = version || templateContext.pull_request_data.pull_request_version || '';
1119 // Pull request case
1128 // Pull request case
1120 if (templateContext.pull_request_data.pull_request_id !== null) {
1129 if (templateContext.pull_request_data.pull_request_id !== null) {
1121 var params = {
1130 var params = {
1122 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1131 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1123 'repo_name': templateContext.repo_name,
1132 'repo_name': templateContext.repo_name,
1124 'version': version,
1133 'version': version,
1125 };
1134 };
1126 var loadUrl = pyroutes.url('pullrequest_comments', params);
1135 var loadUrl = pyroutes.url('pullrequest_comments', params);
1127 } // commit case
1136 } // commit case
1128 else {
1137 else {
1129 return
1138 return
1130 }
1139 }
1131
1140
1132 var currentIDs = []
1141 var currentIDs = []
1133 $.each($('.comment'), function (idx, element) {
1142 $.each($('.comment'), function (idx, element) {
1134 currentIDs.push($(element).data('commentId'));
1143 currentIDs.push($(element).data('commentId'));
1135 });
1144 });
1136
1145
1137 var data = {"comments": currentIDs};
1146 var data = {"comments": currentIDs};
1138 var $targetElem = $('.todos-content-table');
1147 var $targetElem = $('.todos-content-table');
1139 $targetElem.css('opacity', 0.3);
1148 $targetElem.css('opacity', 0.3);
1140
1149
1141 var success = function (data) {
1150 var success = function (data) {
1142 var $counterElem = $('#todos-count')
1151 var $counterElem = $('#todos-count')
1143 var newCount = $(data).data('counter');
1152 var newCount = $(data).data('counter');
1144 if (newCount !== undefined) {
1153 if (newCount !== undefined) {
1145 var callback = function () {
1154 var callback = function () {
1146 $counterElem.animate({'opacity': 1.00}, 200)
1155 $counterElem.animate({'opacity': 1.00}, 200)
1147 $counterElem.html(newCount);
1156 $counterElem.html(newCount);
1148 };
1157 };
1149 $counterElem.animate({'opacity': 0.15}, 200, callback);
1158 $counterElem.animate({'opacity': 0.15}, 200, callback);
1150 }
1159 }
1151
1160
1152 $targetElem.css('opacity', 1);
1161 $targetElem.css('opacity', 1);
1153 $targetElem.html(data);
1162 $targetElem.html(data);
1154 tooltipActivate();
1163 tooltipActivate();
1155 }
1164 }
1156
1165
1157 ajaxPOST(loadUrl, data, success, null, {})
1166 ajaxPOST(loadUrl, data, success, null, {})
1158
1167
1159 }
1168 }
1160
1169
1161 window.refreshAllComments = function (version) {
1170 window.refreshAllComments = function (version) {
1162 version = version || templateContext.pull_request_data.pull_request_version || '';
1171 version = version || templateContext.pull_request_data.pull_request_version || '';
1163
1172
1164 refreshComments(version);
1173 refreshComments(version);
1165 refreshTODOs(version);
1174 refreshTODOs(version);
1166 };
1175 };
1167
1176
1168 window.sidebarComment = function (commentId) {
1177 window.sidebarComment = function (commentId) {
1169 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1178 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1170 if (!jsonData) {
1179 if (!jsonData) {
1171 return 'Failed to load comment {0}'.format(commentId)
1180 return 'Failed to load comment {0}'.format(commentId)
1172 }
1181 }
1173 var funcData = JSON.parse(atob(jsonData));
1182 var funcData = JSON.parse(atob(jsonData));
1174 return renderTemplate('sideBarCommentHovercard', funcData)
1183 return renderTemplate('sideBarCommentHovercard', funcData)
1175 };
1184 };
@@ -1,431 +1,432 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
8
8
9
9
10 <%def name="title()">
10 <%def name="title()">
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='repositories')}
18 ${self.menu_items(active='repositories')}
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_subnav()">
21 <%def name="menu_bar_subnav()">
22 ${self.repo_menu(active='commits')}
22 ${self.repo_menu(active='commits')}
23 </%def>
23 </%def>
24
24
25 <%def name="main()">
25 <%def name="main()">
26 <script type="text/javascript">
26 <script type="text/javascript">
27 // TODO: marcink switch this to pyroutes
27 // TODO: marcink switch this to pyroutes
28 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
28 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
29 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
29 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
30 </script>
30 </script>
31
31
32 <div class="box">
32 <div class="box">
33
33
34 <div class="summary">
34 <div class="summary">
35
35
36 <div class="fieldset">
36 <div class="fieldset">
37 <div class="left-content">
37 <div class="left-content">
38 <%
38 <%
39 rc_user = h.discover_user(c.commit.author_email)
39 rc_user = h.discover_user(c.commit.author_email)
40 %>
40 %>
41 <div class="left-content-avatar">
41 <div class="left-content-avatar">
42 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
42 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
43 </div>
43 </div>
44
44
45 <div class="left-content-message">
45 <div class="left-content-message">
46 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
46 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
47 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
47 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
48 </div>
48 </div>
49
49
50 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
50 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
51 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
51 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
52 </div>
52 </div>
53
53
54 <div class="fieldset" data-toggle="summary-details">
54 <div class="fieldset" data-toggle="summary-details">
55 <div class="">
55 <div class="">
56 <table>
56 <table>
57 <tr class="file_author">
57 <tr class="file_author">
58
58
59 <td>
59 <td>
60 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
60 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
61 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
61 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
62 </td>
62 </td>
63
63
64 <td>
64 <td>
65 ## second cell for consistency with files
65 ## second cell for consistency with files
66 </td>
66 </td>
67 </tr>
67 </tr>
68 </table>
68 </table>
69 </div>
69 </div>
70 </div>
70 </div>
71
71
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="right-content">
75 <div class="right-content">
76
76
77 <div data-toggle="summary-details">
77 <div data-toggle="summary-details">
78 <div class="tags tags-main">
78 <div class="tags tags-main">
79 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
79 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
80 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
80 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
81 ${file_base.refs(c.commit)}
81 ${file_base.refs(c.commit)}
82
82
83 ## phase
83 ## phase
84 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
84 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
85 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
85 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
86 <i class="icon-info"></i>${c.commit.phase}
86 <i class="icon-info"></i>${c.commit.phase}
87 </span>
87 </span>
88 % endif
88 % endif
89
89
90 ## obsolete commits
90 ## obsolete commits
91 % if getattr(c.commit, 'obsolete', False):
91 % if getattr(c.commit, 'obsolete', False):
92 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
92 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
93 ${_('obsolete')}
93 ${_('obsolete')}
94 </span>
94 </span>
95 % endif
95 % endif
96
96
97 ## hidden commits
97 ## hidden commits
98 % if getattr(c.commit, 'hidden', False):
98 % if getattr(c.commit, 'hidden', False):
99 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
99 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
100 ${_('hidden')}
100 ${_('hidden')}
101 </span>
101 </span>
102 % endif
102 % endif
103 </div>
103 </div>
104
104
105 <span id="parent_link" class="tag tagtag">
105 <span id="parent_link" class="tag tagtag">
106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
107 </span>
107 </span>
108
108
109 <span id="child_link" class="tag tagtag">
109 <span id="child_link" class="tag tagtag">
110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
111 </span>
111 </span>
112
112
113 </div>
113 </div>
114
114
115 </div>
115 </div>
116 </div>
116 </div>
117
117
118 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
118 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
119 <div class="left-label-summary">
119 <div class="left-label-summary">
120 <p>${_('Diff options')}:</p>
120 <p>${_('Diff options')}:</p>
121 <div class="right-label-summary">
121 <div class="right-label-summary">
122 <div class="diff-actions">
122 <div class="diff-actions">
123 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
123 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
124 ${_('Raw Diff')}
124 ${_('Raw Diff')}
125 </a>
125 </a>
126 |
126 |
127 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
127 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
128 ${_('Patch Diff')}
128 ${_('Patch Diff')}
129 </a>
129 </a>
130 |
130 |
131 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
131 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
132 ${_('Download Diff')}
132 ${_('Download Diff')}
133 </a>
133 </a>
134 </div>
134 </div>
135 </div>
135 </div>
136 </div>
136 </div>
137 </div>
137 </div>
138
138
139 <div class="clear-fix"></div>
139 <div class="clear-fix"></div>
140
140
141 <div class="btn-collapse" data-toggle="summary-details">
141 <div class="btn-collapse" data-toggle="summary-details">
142 ${_('Show More')}
142 ${_('Show More')}
143 </div>
143 </div>
144
144
145 </div>
145 </div>
146
146
147 <div class="cs_files">
147 <div class="cs_files">
148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
150 ${cbdiffs.render_diffset(
150 ${cbdiffs.render_diffset(
151 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
151 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
152 inline_comments=c.inline_comments,
152 inline_comments=c.inline_comments,
153 show_todos=False)}
153 show_todos=False)}
154 </div>
154 </div>
155
155
156 ## template for inline comment form
156 ## template for inline comment form
157 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
157 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
158
158
159 ## comments heading with count
159 ## comments heading with count
160 <div class="comments-heading">
160 <div class="comments-heading">
161 <i class="icon-comment"></i>
161 <i class="icon-comment"></i>
162 ${_('General Comments')} ${len(c.comments)}
162 ${_('General Comments')} ${len(c.comments)}
163 </div>
163 </div>
164
164
165 ## render comments
165 ## render comments
166 ${comment.generate_comments(c.comments)}
166 ${comment.generate_comments(c.comments)}
167
167
168 ## main comment form and it status
168 ## main comment form and it status
169 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
169 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
171 </div>
171 </div>
172
172
173 ### NAV SIDEBAR
173 ### NAV SIDEBAR
174 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
174 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
175 <div class="sidenav navbar__inner" >
175 <div class="sidenav navbar__inner" >
176 ## TOGGLE
176 ## TOGGLE
177 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
177 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
178 <a href="#toggleSidebar" class="grey-link-action">
178 <a href="#toggleSidebar" class="grey-link-action">
179
179
180 </a>
180 </a>
181 </div>
181 </div>
182
182
183 ## CONTENT
183 ## CONTENT
184 <div class="sidebar-content">
184 <div class="sidebar-content">
185
185
186 ## RULES SUMMARY/RULES
186 ## RULES SUMMARY/RULES
187 <div class="sidebar-element clear-both">
187 <div class="sidebar-element clear-both">
188 <% vote_title = _ungettext(
188 <% vote_title = _ungettext(
189 'Status calculated based on votes from {} reviewer',
189 'Status calculated based on votes from {} reviewer',
190 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
190 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
191 %>
191 %>
192
192
193 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
193 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
194 <i class="icon-circle review-status-${c.commit_review_status}"></i>
194 <i class="icon-circle review-status-${c.commit_review_status}"></i>
195 ${c.reviewers_count}
195 ${c.reviewers_count}
196 </div>
196 </div>
197 </div>
197 </div>
198
198
199 ## REVIEWERS
199 ## REVIEWERS
200 <div class="right-sidebar-expanded-state pr-details-title">
200 <div class="right-sidebar-expanded-state pr-details-title">
201 <span class="tooltip sidebar-heading" title="${vote_title}">
201 <span class="tooltip sidebar-heading" title="${vote_title}">
202 <i class="icon-circle review-status-${c.commit_review_status}"></i>
202 <i class="icon-circle review-status-${c.commit_review_status}"></i>
203 ${_('Reviewers')}
203 ${_('Reviewers')}
204 </span>
204 </span>
205 </div>
205 </div>
206
206
207 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
207 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
208
208
209 <table id="review_members" class="group_members">
209 <table id="review_members" class="group_members">
210 ## This content is loaded via JS and ReviewersPanel
210 ## This content is loaded via JS and ReviewersPanel
211 </table>
211 </table>
212
212
213 </div>
213 </div>
214
214
215 ## TODOs
215 ## TODOs
216 <div class="sidebar-element clear-both">
216 <div class="sidebar-element clear-both">
217 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
217 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
218 <i class="icon-flag-filled"></i>
218 <i class="icon-flag-filled"></i>
219 <span id="todos-count">${len(c.unresolved_comments)}</span>
219 <span id="todos-count">${len(c.unresolved_comments)}</span>
220 </div>
220 </div>
221
221
222 <div class="right-sidebar-expanded-state pr-details-title">
222 <div class="right-sidebar-expanded-state pr-details-title">
223 ## Only show unresolved, that is only what matters
223 ## Only show unresolved, that is only what matters
224 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
224 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
225 <i class="icon-flag-filled"></i>
225 <i class="icon-flag-filled"></i>
226 TODOs
226 TODOs
227 </span>
227 </span>
228
228
229 % if c.resolved_comments:
229 % if c.resolved_comments:
230 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
230 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
231 % else:
231 % else:
232 <span class="block-right last-item noselect">Show resolved</span>
232 <span class="block-right last-item noselect">Show resolved</span>
233 % endif
233 % endif
234
234
235 </div>
235 </div>
236
236
237 <div class="right-sidebar-expanded-state pr-details-content">
237 <div class="right-sidebar-expanded-state pr-details-content">
238 % if c.unresolved_comments + c.resolved_comments:
238 % if c.unresolved_comments + c.resolved_comments:
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
240 % else:
240 % else:
241 <table>
241 <table>
242 <tr>
242 <tr>
243 <td>
243 <td>
244 ${_('No TODOs yet')}
244 ${_('No TODOs yet')}
245 </td>
245 </td>
246 </tr>
246 </tr>
247 </table>
247 </table>
248 % endif
248 % endif
249 </div>
249 </div>
250 </div>
250 </div>
251
251
252 ## COMMENTS
252 ## COMMENTS
253 <div class="sidebar-element clear-both">
253 <div class="sidebar-element clear-both">
254 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
254 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
255 <i class="icon-comment" style="color: #949494"></i>
255 <i class="icon-comment" style="color: #949494"></i>
256 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
256 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
257 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
257 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
258 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
258 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
259 </div>
259 </div>
260
260
261 <div class="right-sidebar-expanded-state pr-details-title">
261 <div class="right-sidebar-expanded-state pr-details-title">
262 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
262 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
263 <i class="icon-comment" style="color: #949494"></i>
263 <i class="icon-comment" style="color: #949494"></i>
264 ${_('Comments')}
264 ${_('Comments')}
265 </span>
265 </span>
266
266
267 </div>
267 </div>
268
268
269 <div class="right-sidebar-expanded-state pr-details-content">
269 <div class="right-sidebar-expanded-state pr-details-content">
270 % if c.inline_comments_flat + c.comments:
270 % if c.inline_comments_flat + c.comments:
271 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
271 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
272 % else:
272 % else:
273 <table>
273 <table>
274 <tr>
274 <tr>
275 <td>
275 <td>
276 ${_('No Comments yet')}
276 ${_('No Comments yet')}
277 </td>
277 </td>
278 </tr>
278 </tr>
279 </table>
279 </table>
280 % endif
280 % endif
281 </div>
281 </div>
282
282
283 </div>
283 </div>
284
284
285 </div>
285 </div>
286
286
287 </div>
287 </div>
288 </aside>
288 </aside>
289
289
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
291 <script type="text/javascript">
291 <script type="text/javascript">
292 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
292 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
293
293
294 $(document).ready(function () {
294 $(document).ready(function () {
295 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
295 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
296
296
297 if ($('#trimmed_message_box').height() === boxmax) {
297 if ($('#trimmed_message_box').height() === boxmax) {
298 $('#message_expand').show();
298 $('#message_expand').show();
299 }
299 }
300
300
301 $('#message_expand').on('click', function (e) {
301 $('#message_expand').on('click', function (e) {
302 $('#trimmed_message_box').css('max-height', 'none');
302 $('#trimmed_message_box').css('max-height', 'none');
303 $(this).hide();
303 $(this).hide();
304 });
304 });
305
305
306 $('.show-inline-comments').on('click', function (e) {
306 $('.show-inline-comments').on('click', function (e) {
307 var boxid = $(this).attr('data-comment-id');
307 var boxid = $(this).attr('data-comment-id');
308 var button = $(this);
308 var button = $(this);
309
309
310 if (button.hasClass("comments-visible")) {
310 if (button.hasClass("comments-visible")) {
311 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
311 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
312 $(this).hide();
312 $(this).hide();
313 });
313 });
314 button.removeClass("comments-visible");
314 button.removeClass("comments-visible");
315 } else {
315 } else {
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
317 $(this).show();
317 $(this).show();
318 });
318 });
319 button.addClass("comments-visible");
319 button.addClass("comments-visible");
320 }
320 }
321 });
321 });
322
322
323 // next links
323 // next links
324 $('#child_link').on('click', function (e) {
324 $('#child_link').on('click', function (e) {
325 // fetch via ajax what is going to be the next link, if we have
325 // fetch via ajax what is going to be the next link, if we have
326 // >1 links show them to user to choose
326 // >1 links show them to user to choose
327 if (!$('#child_link').hasClass('disabled')) {
327 if (!$('#child_link').hasClass('disabled')) {
328 $.ajax({
328 $.ajax({
329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
330 success: function (data) {
330 success: function (data) {
331 if (data.results.length === 0) {
331 if (data.results.length === 0) {
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
333 }
333 }
334 if (data.results.length === 1) {
334 if (data.results.length === 1) {
335 var commit = data.results[0];
335 var commit = data.results[0];
336 window.location = pyroutes.url('repo_commit', {
336 window.location = pyroutes.url('repo_commit', {
337 'repo_name': '${c.repo_name}',
337 'repo_name': '${c.repo_name}',
338 'commit_id': commit.raw_id
338 'commit_id': commit.raw_id
339 });
339 });
340 } else if (data.results.length === 2) {
340 } else if (data.results.length === 2) {
341 $('#child_link').addClass('disabled');
341 $('#child_link').addClass('disabled');
342 $('#child_link').addClass('double');
342 $('#child_link').addClass('double');
343
343
344 var _html = '';
344 var _html = '';
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
346 .replace('__branch__', data.results[0].branch)
346 .replace('__branch__', data.results[0].branch)
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
348 .replace('__title__', data.results[0].message)
348 .replace('__title__', data.results[0].message)
349 .replace('__url__', pyroutes.url('repo_commit', {
349 .replace('__url__', pyroutes.url('repo_commit', {
350 'repo_name': '${c.repo_name}',
350 'repo_name': '${c.repo_name}',
351 'commit_id': data.results[0].raw_id
351 'commit_id': data.results[0].raw_id
352 }));
352 }));
353 _html += ' | ';
353 _html += ' | ';
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
355 .replace('__branch__', data.results[1].branch)
355 .replace('__branch__', data.results[1].branch)
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
357 .replace('__title__', data.results[1].message)
357 .replace('__title__', data.results[1].message)
358 .replace('__url__', pyroutes.url('repo_commit', {
358 .replace('__url__', pyroutes.url('repo_commit', {
359 'repo_name': '${c.repo_name}',
359 'repo_name': '${c.repo_name}',
360 'commit_id': data.results[1].raw_id
360 'commit_id': data.results[1].raw_id
361 }));
361 }));
362 $('#child_link').html(_html);
362 $('#child_link').html(_html);
363 }
363 }
364 }
364 }
365 });
365 });
366 e.preventDefault();
366 e.preventDefault();
367 }
367 }
368 });
368 });
369
369
370 // prev links
370 // prev links
371 $('#parent_link').on('click', function (e) {
371 $('#parent_link').on('click', function (e) {
372 // fetch via ajax what is going to be the next link, if we have
372 // fetch via ajax what is going to be the next link, if we have
373 // >1 links show them to user to choose
373 // >1 links show them to user to choose
374 if (!$('#parent_link').hasClass('disabled')) {
374 if (!$('#parent_link').hasClass('disabled')) {
375 $.ajax({
375 $.ajax({
376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
377 success: function (data) {
377 success: function (data) {
378 if (data.results.length === 0) {
378 if (data.results.length === 0) {
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
380 }
380 }
381 if (data.results.length === 1) {
381 if (data.results.length === 1) {
382 var commit = data.results[0];
382 var commit = data.results[0];
383 window.location = pyroutes.url('repo_commit', {
383 window.location = pyroutes.url('repo_commit', {
384 'repo_name': '${c.repo_name}',
384 'repo_name': '${c.repo_name}',
385 'commit_id': commit.raw_id
385 'commit_id': commit.raw_id
386 });
386 });
387 } else if (data.results.length === 2) {
387 } else if (data.results.length === 2) {
388 $('#parent_link').addClass('disabled');
388 $('#parent_link').addClass('disabled');
389 $('#parent_link').addClass('double');
389 $('#parent_link').addClass('double');
390
390
391 var _html = '';
391 var _html = '';
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
393 .replace('__branch__', data.results[0].branch)
393 .replace('__branch__', data.results[0].branch)
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
395 .replace('__title__', data.results[0].message)
395 .replace('__title__', data.results[0].message)
396 .replace('__url__', pyroutes.url('repo_commit', {
396 .replace('__url__', pyroutes.url('repo_commit', {
397 'repo_name': '${c.repo_name}',
397 'repo_name': '${c.repo_name}',
398 'commit_id': data.results[0].raw_id
398 'commit_id': data.results[0].raw_id
399 }));
399 }));
400 _html += ' | ';
400 _html += ' | ';
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
402 .replace('__branch__', data.results[1].branch)
402 .replace('__branch__', data.results[1].branch)
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
404 .replace('__title__', data.results[1].message)
404 .replace('__title__', data.results[1].message)
405 .replace('__url__', pyroutes.url('repo_commit', {
405 .replace('__url__', pyroutes.url('repo_commit', {
406 'repo_name': '${c.repo_name}',
406 'repo_name': '${c.repo_name}',
407 'commit_id': data.results[1].raw_id
407 'commit_id': data.results[1].raw_id
408 }));
408 }));
409 $('#parent_link').html(_html);
409 $('#parent_link').html(_html);
410 }
410 }
411 }
411 }
412 });
412 });
413 e.preventDefault();
413 e.preventDefault();
414 }
414 }
415 });
415 });
416
416
417 // browse tree @ revision
417 // browse tree @ revision
418 $('#files_link').on('click', function (e) {
418 $('#files_link').on('click', function (e) {
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
420 e.preventDefault();
420 e.preventDefault();
421 });
421 });
422
422
423 ReviewersPanel.init(null, setReviewersData);
423 reviewersController = new ReviewersController();
424 ReviewersPanel.init(reviewersController, null, setReviewersData);
424
425
425 var channel = '${c.commit_broadcast_channel}';
426 var channel = '${c.commit_broadcast_channel}';
426 new ReviewerPresenceController(channel)
427 new ReviewerPresenceController(channel)
427
428
428 })
429 })
429 </script>
430 </script>
430
431
431 </%def>
432 </%def>
@@ -1,994 +1,994 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
5
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15
15
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='showpullrequest')}
23 ${self.repo_menu(active='showpullrequest')}
24 </%def>
24 </%def>
25
25
26
26
27 <%def name="main()">
27 <%def name="main()">
28 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
29 <%
29 <%
30 c.referenced_commit_issues = []
30 c.referenced_commit_issues = []
31 c.referenced_desc_issues = []
31 c.referenced_desc_issues = []
32 %>
32 %>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
39 </script>
39 </script>
40
40
41 <div class="box">
41 <div class="box">
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <div id="pr-title">
46 <div id="pr-title">
47 % if c.pull_request.is_closed():
47 % if c.pull_request.is_closed():
48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
49 % endif
49 % endif
50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 </div>
51 </div>
52 <div id="pr-title-edit" class="input" style="display: none;">
52 <div id="pr-title-edit" class="input" style="display: none;">
53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
54 </div>
54 </div>
55
55
56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
57 <div class="pr-details-title">
57 <div class="pr-details-title">
58 <div class="pull-left">
58 <div class="pull-left">
59 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
59 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a>
60 ${_('Created on')}
60 ${_('Created on')}
61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
62 <span class="pr-details-title-author-pref">${_('by')}</span>
62 <span class="pr-details-title-author-pref">${_('by')}</span>
63 </div>
63 </div>
64
64
65 <div class="pull-left">
65 <div class="pull-left">
66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
67 </div>
67 </div>
68
68
69 %if c.allowed_to_update:
69 %if c.allowed_to_update:
70 <div class="pull-right">
70 <div class="pull-right">
71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
73 % if c.allowed_to_delete:
73 % if c.allowed_to_delete:
74 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
74 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
77 type="submit" value="${_('Delete pull request')}">
77 type="submit" value="${_('Delete pull request')}">
78 ${h.end_form()}
78 ${h.end_form()}
79 % else:
79 % else:
80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
81 % endif
81 % endif
82 </div>
82 </div>
83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
85 </div>
85 </div>
86
86
87 %endif
87 %endif
88 </div>
88 </div>
89
89
90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
92 </div>
92 </div>
93
93
94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
97 </div>
97 </div>
98
98
99 <div id="summary" class="fields pr-details-content">
99 <div id="summary" class="fields pr-details-content">
100
100
101 ## source
101 ## source
102 <div class="field">
102 <div class="field">
103 <div class="label-pr-detail">
103 <div class="label-pr-detail">
104 <label>${_('Commit flow')}:</label>
104 <label>${_('Commit flow')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-commit-flow">
107 <div class="pr-commit-flow">
108 ## Source
108 ## Source
109 %if c.pull_request.source_ref_parts.type == 'branch':
109 %if c.pull_request.source_ref_parts.type == 'branch':
110 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
110 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
111 %else:
111 %else:
112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
113 %endif
113 %endif
114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
115 &rarr;
115 &rarr;
116 ## Target
116 ## Target
117 %if c.pull_request.target_ref_parts.type == 'branch':
117 %if c.pull_request.target_ref_parts.type == 'branch':
118 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
118 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
119 %else:
119 %else:
120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
121 %endif
121 %endif
122
122
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
124
124
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
126 <i class="icon-angle-down">more details</i>
126 <i class="icon-angle-down">more details</i>
127 </a>
127 </a>
128
128
129 </div>
129 </div>
130
130
131 <div class="source-details" style="display: none">
131 <div class="source-details" style="display: none">
132
132
133 <ul>
133 <ul>
134
134
135 ## common ancestor
135 ## common ancestor
136 <li>
136 <li>
137 ${_('Common ancestor')}:
137 ${_('Common ancestor')}:
138 % if c.ancestor_commit:
138 % if c.ancestor_commit:
139 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
139 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a>
140 % else:
140 % else:
141 ${_('not available')}
141 ${_('not available')}
142 % endif
142 % endif
143 </li>
143 </li>
144
144
145 ## pull url
145 ## pull url
146 <li>
146 <li>
147 %if h.is_hg(c.pull_request.source_repo):
147 %if h.is_hg(c.pull_request.source_repo):
148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
149 %elif h.is_git(c.pull_request.source_repo):
149 %elif h.is_git(c.pull_request.source_repo):
150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
151 %endif
151 %endif
152
152
153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
155 </li>
155 </li>
156
156
157 ## Shadow repo
157 ## Shadow repo
158 <li>
158 <li>
159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
160 %if h.is_hg(c.pull_request.target_repo):
160 %if h.is_hg(c.pull_request.target_repo):
161 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
161 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
162 %elif h.is_git(c.pull_request.target_repo):
162 %elif h.is_git(c.pull_request.target_repo):
163 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
163 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
164 %endif
164 %endif
165
165
166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
168
168
169 % else:
169 % else:
170 <div class="">
170 <div class="">
171 ${_('Shadow repository data not available')}.
171 ${_('Shadow repository data not available')}.
172 </div>
172 </div>
173 % endif
173 % endif
174 </li>
174 </li>
175
175
176 </ul>
176 </ul>
177
177
178 </div>
178 </div>
179
179
180 </div>
180 </div>
181
181
182 </div>
182 </div>
183
183
184 ## versions
184 ## versions
185 <div class="field">
185 <div class="field">
186 <div class="label-pr-detail">
186 <div class="label-pr-detail">
187 <label>${_('Versions')}:</label>
187 <label>${_('Versions')}:</label>
188 </div>
188 </div>
189
189
190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
192
192
193 <div class="pr-versions">
193 <div class="pr-versions">
194 % if c.show_version_changes:
194 % if c.show_version_changes:
195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
199 data-toggle-on="${_('show versions')}."
199 data-toggle-on="${_('show versions')}."
200 data-toggle-off="${_('hide versions')}.">
200 data-toggle-off="${_('hide versions')}.">
201 ${_('show versions')}.
201 ${_('show versions')}.
202 </a>
202 </a>
203 <table>
203 <table>
204 ## SHOW ALL VERSIONS OF PR
204 ## SHOW ALL VERSIONS OF PR
205 <% ver_pr = None %>
205 <% ver_pr = None %>
206
206
207 % for data in reversed(list(enumerate(c.versions, 1))):
207 % for data in reversed(list(enumerate(c.versions, 1))):
208 <% ver_pos = data[0] %>
208 <% ver_pos = data[0] %>
209 <% ver = data[1] %>
209 <% ver = data[1] %>
210 <% ver_pr = ver.pull_request_version_id %>
210 <% ver_pr = ver.pull_request_version_id %>
211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
212
212
213 <tr class="version-pr" style="display: ${display_row}">
213 <tr class="version-pr" style="display: ${display_row}">
214 <td>
214 <td>
215 <code>
215 <code>
216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
217 </code>
217 </code>
218 </td>
218 </td>
219 <td>
219 <td>
220 <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}"/>
220 <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}"/>
221 <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}"/>
221 <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}"/>
222 </td>
222 </td>
223 <td>
223 <td>
224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
226
226
227 </td>
227 </td>
228 <td>
228 <td>
229 % if c.at_version_num != ver_pr:
229 % if c.at_version_num != ver_pr:
230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
231 <code>
231 <code>
232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
233 </code>
233 </code>
234 % endif
234 % endif
235 </td>
235 </td>
236 <td>
236 <td>
237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
238 </td>
238 </td>
239 <td>
239 <td>
240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
241 </td>
241 </td>
242 </tr>
242 </tr>
243 % endfor
243 % endfor
244
244
245 <tr>
245 <tr>
246 <td colspan="6">
246 <td colspan="6">
247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
248 data-label-text-locked="${_('select versions to show changes')}"
248 data-label-text-locked="${_('select versions to show changes')}"
249 data-label-text-diff="${_('show changes between versions')}"
249 data-label-text-diff="${_('show changes between versions')}"
250 data-label-text-show="${_('show pull request for this version')}"
250 data-label-text-show="${_('show pull request for this version')}"
251 >
251 >
252 ${_('select versions to show changes')}
252 ${_('select versions to show changes')}
253 </button>
253 </button>
254 </td>
254 </td>
255 </tr>
255 </tr>
256 </table>
256 </table>
257 % else:
257 % else:
258 <div>
258 <div>
259 ${_('Pull request versions not available')}.
259 ${_('Pull request versions not available')}.
260 </div>
260 </div>
261 % endif
261 % endif
262 </div>
262 </div>
263 </div>
263 </div>
264
264
265 </div>
265 </div>
266
266
267 </div>
267 </div>
268
268
269
269
270 </div>
270 </div>
271
271
272 </div>
272 </div>
273
273
274 <div class="box">
274 <div class="box">
275
275
276 % if c.state_progressing:
276 % if c.state_progressing:
277
277
278 <h2 style="text-align: center">
278 <h2 style="text-align: center">
279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
280
280
281 % if c.is_super_admin:
281 % if c.is_super_admin:
282 <br/>
282 <br/>
283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
284 % endif
284 % endif
285 </h2>
285 </h2>
286
286
287 % else:
287 % else:
288
288
289 ## Diffs rendered here
289 ## Diffs rendered here
290 <div class="table" >
290 <div class="table" >
291 <div id="changeset_compare_view_content">
291 <div id="changeset_compare_view_content">
292 ##CS
292 ##CS
293 % if c.missing_requirements:
293 % if c.missing_requirements:
294 <div class="box">
294 <div class="box">
295 <div class="alert alert-warning">
295 <div class="alert alert-warning">
296 <div>
296 <div>
297 <strong>${_('Missing requirements:')}</strong>
297 <strong>${_('Missing requirements:')}</strong>
298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
299 </div>
299 </div>
300 </div>
300 </div>
301 </div>
301 </div>
302 % elif c.missing_commits:
302 % elif c.missing_commits:
303 <div class="box">
303 <div class="box">
304 <div class="alert alert-warning">
304 <div class="alert alert-warning">
305 <div>
305 <div>
306 <strong>${_('Missing commits')}:</strong>
306 <strong>${_('Missing commits')}:</strong>
307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
310 </div>
310 </div>
311 </div>
311 </div>
312 </div>
312 </div>
313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
314 <div class="box">
314 <div class="box">
315 <div class="alert alert-info">
315 <div class="alert alert-info">
316 <div>
316 <div>
317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
318 </div>
318 </div>
319 </div>
319 </div>
320 </div>
320 </div>
321 % endif
321 % endif
322
322
323 <div class="compare_view_commits_title">
323 <div class="compare_view_commits_title">
324 % if not c.compare_mode:
324 % if not c.compare_mode:
325
325
326 % if c.at_version_index:
326 % if c.at_version_index:
327 <h4>
327 <h4>
328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
329 </h4>
329 </h4>
330 % endif
330 % endif
331
331
332 <div class="pull-left">
332 <div class="pull-left">
333 <div class="btn-group">
333 <div class="btn-group">
334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
335 % if c.collapse_all_commits:
335 % if c.collapse_all_commits:
336 <i class="icon-plus-squared-alt icon-no-margin"></i>
336 <i class="icon-plus-squared-alt icon-no-margin"></i>
337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 % else:
338 % else:
339 <i class="icon-minus-squared-alt icon-no-margin"></i>
339 <i class="icon-minus-squared-alt icon-no-margin"></i>
340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
341 % endif
341 % endif
342 </a>
342 </a>
343 </div>
343 </div>
344 </div>
344 </div>
345
345
346 <div class="pull-right">
346 <div class="pull-right">
347 % if c.allowed_to_update and not c.pull_request.is_closed():
347 % if c.allowed_to_update and not c.pull_request.is_closed():
348
348
349 <div class="btn-group btn-group-actions">
349 <div class="btn-group btn-group-actions">
350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
351 ${_('Update commits')}
351 ${_('Update commits')}
352 </a>
352 </a>
353
353
354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
355 <i class="icon-down"></i>
355 <i class="icon-down"></i>
356 </a>
356 </a>
357
357
358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
360 <li>
360 <li>
361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
362 ${_('Force update commits')}
362 ${_('Force update commits')}
363 </a>
363 </a>
364 <div class="action-help-block">
364 <div class="action-help-block">
365 ${_('Update commits and force refresh this pull request.')}
365 ${_('Update commits and force refresh this pull request.')}
366 </div>
366 </div>
367 </li>
367 </li>
368 </ul>
368 </ul>
369 </div>
369 </div>
370 </div>
370 </div>
371
371
372 % else:
372 % else:
373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
374 % endif
374 % endif
375
375
376 </div>
376 </div>
377 % endif
377 % endif
378 </div>
378 </div>
379
379
380 % if not c.missing_commits:
380 % if not c.missing_commits:
381 ## COMPARE RANGE DIFF MODE
381 ## COMPARE RANGE DIFF MODE
382 % if c.compare_mode:
382 % if c.compare_mode:
383 % if c.at_version:
383 % if c.at_version:
384 <h4>
384 <h4>
385 ${_('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')}:
385 ${_('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')}:
386 </h4>
386 </h4>
387
387
388 <div class="subtitle-compare">
388 <div class="subtitle-compare">
389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
390 </div>
390 </div>
391
391
392 <div class="container">
392 <div class="container">
393 <table class="rctable compare_view_commits">
393 <table class="rctable compare_view_commits">
394 <tr>
394 <tr>
395 <th></th>
395 <th></th>
396 <th>${_('Time')}</th>
396 <th>${_('Time')}</th>
397 <th>${_('Author')}</th>
397 <th>${_('Author')}</th>
398 <th>${_('Commit')}</th>
398 <th>${_('Commit')}</th>
399 <th></th>
399 <th></th>
400 <th>${_('Description')}</th>
400 <th>${_('Description')}</th>
401 </tr>
401 </tr>
402
402
403 % for c_type, commit in c.commit_changes:
403 % for c_type, commit in c.commit_changes:
404 % if c_type in ['a', 'r']:
404 % if c_type in ['a', 'r']:
405 <%
405 <%
406 if c_type == 'a':
406 if c_type == 'a':
407 cc_title = _('Commit added in displayed changes')
407 cc_title = _('Commit added in displayed changes')
408 elif c_type == 'r':
408 elif c_type == 'r':
409 cc_title = _('Commit removed in displayed changes')
409 cc_title = _('Commit removed in displayed changes')
410 else:
410 else:
411 cc_title = ''
411 cc_title = ''
412 %>
412 %>
413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
414 <td>
414 <td>
415 <div class="commit-change-indicator color-${c_type}-border">
415 <div class="commit-change-indicator color-${c_type}-border">
416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
417 ${c_type.upper()}
417 ${c_type.upper()}
418 </div>
418 </div>
419 </div>
419 </div>
420 </td>
420 </td>
421 <td class="td-time">
421 <td class="td-time">
422 ${h.age_component(commit.date)}
422 ${h.age_component(commit.date)}
423 </td>
423 </td>
424 <td class="td-user">
424 <td class="td-user">
425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
426 </td>
426 </td>
427 <td class="td-hash">
427 <td class="td-hash">
428 <code>
428 <code>
429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
430 r${commit.idx}:${h.short_id(commit.raw_id)}
430 r${commit.idx}:${h.short_id(commit.raw_id)}
431 </a>
431 </a>
432 ${h.hidden('revisions', commit.raw_id)}
432 ${h.hidden('revisions', commit.raw_id)}
433 </code>
433 </code>
434 </td>
434 </td>
435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
436 <i class="icon-expand-linked"></i>
436 <i class="icon-expand-linked"></i>
437 </td>
437 </td>
438 <td class="mid td-description">
438 <td class="mid td-description">
439 <div class="log-container truncate-wrap">
439 <div class="log-container truncate-wrap">
440 <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>
440 <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>
441 </div>
441 </div>
442 </td>
442 </td>
443 </tr>
443 </tr>
444 % endif
444 % endif
445 % endfor
445 % endfor
446 </table>
446 </table>
447 </div>
447 </div>
448
448
449 % endif
449 % endif
450
450
451 ## Regular DIFF
451 ## Regular DIFF
452 % else:
452 % else:
453 <%include file="/compare/compare_commits.mako" />
453 <%include file="/compare/compare_commits.mako" />
454 % endif
454 % endif
455
455
456 <div class="cs_files">
456 <div class="cs_files">
457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
458
458
459 <%
459 <%
460 pr_menu_data = {
460 pr_menu_data = {
461 'outdated_comm_count_ver': outdated_comm_count_ver,
461 'outdated_comm_count_ver': outdated_comm_count_ver,
462 'pull_request': c.pull_request
462 'pull_request': c.pull_request
463 }
463 }
464 %>
464 %>
465
465
466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
467
467
468 % if c.range_diff_on:
468 % if c.range_diff_on:
469 % for commit in c.commit_ranges:
469 % for commit in c.commit_ranges:
470 ${cbdiffs.render_diffset(
470 ${cbdiffs.render_diffset(
471 c.changes[commit.raw_id],
471 c.changes[commit.raw_id],
472 commit=commit, use_comments=True,
472 commit=commit, use_comments=True,
473 collapse_when_files_over=5,
473 collapse_when_files_over=5,
474 disable_new_comments=True,
474 disable_new_comments=True,
475 deleted_files_comments=c.deleted_files_comments,
475 deleted_files_comments=c.deleted_files_comments,
476 inline_comments=c.inline_comments,
476 inline_comments=c.inline_comments,
477 pull_request_menu=pr_menu_data, show_todos=False)}
477 pull_request_menu=pr_menu_data, show_todos=False)}
478 % endfor
478 % endfor
479 % else:
479 % else:
480 ${cbdiffs.render_diffset(
480 ${cbdiffs.render_diffset(
481 c.diffset, use_comments=True,
481 c.diffset, use_comments=True,
482 collapse_when_files_over=30,
482 collapse_when_files_over=30,
483 disable_new_comments=not c.allowed_to_comment,
483 disable_new_comments=not c.allowed_to_comment,
484 deleted_files_comments=c.deleted_files_comments,
484 deleted_files_comments=c.deleted_files_comments,
485 inline_comments=c.inline_comments,
485 inline_comments=c.inline_comments,
486 pull_request_menu=pr_menu_data, show_todos=False)}
486 pull_request_menu=pr_menu_data, show_todos=False)}
487 % endif
487 % endif
488
488
489 </div>
489 </div>
490 % else:
490 % else:
491 ## skipping commits we need to clear the view for missing commits
491 ## skipping commits we need to clear the view for missing commits
492 <div style="clear:both;"></div>
492 <div style="clear:both;"></div>
493 % endif
493 % endif
494
494
495 </div>
495 </div>
496 </div>
496 </div>
497
497
498 ## template for inline comment form
498 ## template for inline comment form
499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
500
500
501 ## comments heading with count
501 ## comments heading with count
502 <div class="comments-heading">
502 <div class="comments-heading">
503 <i class="icon-comment"></i>
503 <i class="icon-comment"></i>
504 ${_('General Comments')} ${len(c.comments)}
504 ${_('General Comments')} ${len(c.comments)}
505 </div>
505 </div>
506
506
507 ## render general comments
507 ## render general comments
508 <div id="comment-tr-show">
508 <div id="comment-tr-show">
509 % if general_outdated_comm_count_ver:
509 % if general_outdated_comm_count_ver:
510 <div class="info-box">
510 <div class="info-box">
511 % if general_outdated_comm_count_ver == 1:
511 % if general_outdated_comm_count_ver == 1:
512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
514 % else:
514 % else:
515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
517 % endif
517 % endif
518 </div>
518 </div>
519 % endif
519 % endif
520 </div>
520 </div>
521
521
522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
523
523
524 % if not c.pull_request.is_closed():
524 % if not c.pull_request.is_closed():
525 ## main comment form and it status
525 ## main comment form and it status
526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
527 pull_request_id=c.pull_request.pull_request_id),
527 pull_request_id=c.pull_request.pull_request_id),
528 c.pull_request_review_status,
528 c.pull_request_review_status,
529 is_pull_request=True, change_status=c.allowed_to_change_status)}
529 is_pull_request=True, change_status=c.allowed_to_change_status)}
530
530
531 ## merge status, and merge action
531 ## merge status, and merge action
532 <div class="pull-request-merge">
532 <div class="pull-request-merge">
533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
534 </div>
534 </div>
535
535
536 %endif
536 %endif
537
537
538 % endif
538 % endif
539 </div>
539 </div>
540
540
541
541
542 ### NAV SIDEBAR
542 ### NAV SIDEBAR
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
544 <div class="sidenav navbar__inner" >
544 <div class="sidenav navbar__inner" >
545 ## TOGGLE
545 ## TOGGLE
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
547 <a href="#toggleSidebar" class="grey-link-action">
547 <a href="#toggleSidebar" class="grey-link-action">
548
548
549 </a>
549 </a>
550 </div>
550 </div>
551
551
552 ## CONTENT
552 ## CONTENT
553 <div class="sidebar-content">
553 <div class="sidebar-content">
554
554
555 ## RULES SUMMARY/RULES
555 ## RULES SUMMARY/RULES
556 <div class="sidebar-element clear-both">
556 <div class="sidebar-element clear-both">
557 <% vote_title = _ungettext(
557 <% vote_title = _ungettext(
558 'Status calculated based on votes from {} reviewer',
558 'Status calculated based on votes from {} reviewer',
559 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
559 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
560 %>
560 %>
561
561
562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
564 ${c.reviewers_count}
564 ${c.reviewers_count}
565 </div>
565 </div>
566
566
567 ## REVIEW RULES
567 ## REVIEW RULES
568 <div id="review_rules" style="display: none" class="">
568 <div id="review_rules" style="display: none" class="">
569 <div class="right-sidebar-expanded-state pr-details-title">
569 <div class="right-sidebar-expanded-state pr-details-title">
570 <span class="sidebar-heading">
570 <span class="sidebar-heading">
571 ${_('Reviewer rules')}
571 ${_('Reviewer rules')}
572 </span>
572 </span>
573
573
574 </div>
574 </div>
575 <div class="pr-reviewer-rules">
575 <div class="pr-reviewer-rules">
576 ## review rules will be appended here, by default reviewers logic
576 ## review rules will be appended here, by default reviewers logic
577 </div>
577 </div>
578 <input id="review_data" type="hidden" name="review_data" value="">
578 <input id="review_data" type="hidden" name="review_data" value="">
579 </div>
579 </div>
580
580
581 ## REVIEWERS
581 ## REVIEWERS
582 <div class="right-sidebar-expanded-state pr-details-title">
582 <div class="right-sidebar-expanded-state pr-details-title">
583 <span class="tooltip sidebar-heading" title="${vote_title}">
583 <span class="tooltip sidebar-heading" title="${vote_title}">
584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
585 ${_('Reviewers')}
585 ${_('Reviewers')}
586 </span>
586 </span>
587 %if c.allowed_to_update:
587 %if c.allowed_to_update:
588 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
588 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
589 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
589 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
590 %else:
590 %else:
591 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
591 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
592 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
592 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
593 %endif
593 %endif
594 </div>
594 </div>
595
595
596 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
596 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
597
597
598 ## members redering block
598 ## members redering block
599 <input type="hidden" name="__start__" value="review_members:sequence">
599 <input type="hidden" name="__start__" value="review_members:sequence">
600
600
601 <table id="review_members" class="group_members">
601 <table id="review_members" class="group_members">
602 ## This content is loaded via JS and ReviewersPanel
602 ## This content is loaded via JS and ReviewersPanel
603 </table>
603 </table>
604
604
605 <input type="hidden" name="__end__" value="review_members:sequence">
605 <input type="hidden" name="__end__" value="review_members:sequence">
606 ## end members redering block
606 ## end members redering block
607
607
608 %if not c.pull_request.is_closed():
608 %if not c.pull_request.is_closed():
609 <div id="add_reviewer" class="ac" style="display: none;">
609 <div id="add_reviewer" class="ac" style="display: none;">
610 %if c.allowed_to_update:
610 %if c.allowed_to_update:
611 % if not c.forbid_adding_reviewers:
611 % if not c.forbid_adding_reviewers:
612 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
612 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
613 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
613 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
614 <div id="reviewers_container"></div>
614 <div id="reviewers_container"></div>
615 </div>
615 </div>
616 % endif
616 % endif
617 <div class="pull-right" style="margin-bottom: 15px">
617 <div class="pull-right" style="margin-bottom: 15px">
618 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
618 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
619 </div>
619 </div>
620 %endif
620 %endif
621 </div>
621 </div>
622 %endif
622 %endif
623 </div>
623 </div>
624 </div>
624 </div>
625
625
626 ## OBSERVERS
626 ## OBSERVERS
627 <div class="sidebar-element clear-both">
627 <div class="sidebar-element clear-both">
628 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
628 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Observers')}">
629 <i class="icon-circle-thin"></i>
629 <i class="icon-circle-thin"></i>
630 ${c.observers_count}
630 ${c.observers_count}
631 </div>
631 </div>
632
632
633 <div class="right-sidebar-expanded-state pr-details-title">
633 <div class="right-sidebar-expanded-state pr-details-title">
634 <span class="sidebar-heading">
634 <span class="sidebar-heading">
635 <i class="icon-circle-thin"></i>
635 <i class="icon-circle-thin"></i>
636 ${_('Observers')}
636 ${_('Observers')}
637 </span>
637 </span>
638 %if c.allowed_to_update:
638 %if c.allowed_to_update:
639 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
639 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
640 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
640 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
641 %endif
641 %endif
642 </div>
642 </div>
643
643
644 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
644 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
645 ## members redering block
645 ## members redering block
646 <input type="hidden" name="__start__" value="observer_members:sequence">
646 <input type="hidden" name="__start__" value="observer_members:sequence">
647
647
648 <table id="observer_members" class="group_members">
648 <table id="observer_members" class="group_members">
649 ## This content is loaded via JS and ReviewersPanel
649 ## This content is loaded via JS and ReviewersPanel
650 </table>
650 </table>
651
651
652 <input type="hidden" name="__end__" value="observer_members:sequence">
652 <input type="hidden" name="__end__" value="observer_members:sequence">
653 ## end members redering block
653 ## end members redering block
654
654
655 %if not c.pull_request.is_closed():
655 %if not c.pull_request.is_closed():
656 <div id="add_observer" class="ac" style="display: none;">
656 <div id="add_observer" class="ac" style="display: none;">
657 %if c.allowed_to_update:
657 %if c.allowed_to_update:
658 % if not c.forbid_adding_reviewers or 1:
658 % if not c.forbid_adding_reviewers or 1:
659 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
659 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
660 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
660 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
661 <div id="observers_container"></div>
661 <div id="observers_container"></div>
662 </div>
662 </div>
663 % endif
663 % endif
664 <div class="pull-right" style="margin-bottom: 15px">
664 <div class="pull-right" style="margin-bottom: 15px">
665 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
665 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
666 </div>
666 </div>
667 %endif
667 %endif
668 </div>
668 </div>
669 %endif
669 %endif
670 </div>
670 </div>
671 </div>
671 </div>
672
672
673 ## TODOs
673 ## TODOs
674 <div class="sidebar-element clear-both">
674 <div class="sidebar-element clear-both">
675 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
675 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
676 <i class="icon-flag-filled"></i>
676 <i class="icon-flag-filled"></i>
677 <span id="todos-count">${len(c.unresolved_comments)}</span>
677 <span id="todos-count">${len(c.unresolved_comments)}</span>
678 </div>
678 </div>
679
679
680 <div class="right-sidebar-expanded-state pr-details-title">
680 <div class="right-sidebar-expanded-state pr-details-title">
681 ## Only show unresolved, that is only what matters
681 ## Only show unresolved, that is only what matters
682 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
682 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
683 <i class="icon-flag-filled"></i>
683 <i class="icon-flag-filled"></i>
684 TODOs
684 TODOs
685 </span>
685 </span>
686
686
687 % if not c.at_version:
687 % if not c.at_version:
688 % if c.resolved_comments:
688 % if c.resolved_comments:
689 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
689 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
690 % else:
690 % else:
691 <span class="block-right last-item noselect">Show resolved</span>
691 <span class="block-right last-item noselect">Show resolved</span>
692 % endif
692 % endif
693 % endif
693 % endif
694 </div>
694 </div>
695
695
696 <div class="right-sidebar-expanded-state pr-details-content">
696 <div class="right-sidebar-expanded-state pr-details-content">
697
697
698 % if c.at_version:
698 % if c.at_version:
699 <table>
699 <table>
700 <tr>
700 <tr>
701 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
701 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
702 </tr>
702 </tr>
703 </table>
703 </table>
704 % else:
704 % else:
705 % if c.unresolved_comments + c.resolved_comments:
705 % if c.unresolved_comments + c.resolved_comments:
706 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
706 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
707 % else:
707 % else:
708 <table>
708 <table>
709 <tr>
709 <tr>
710 <td>
710 <td>
711 ${_('No TODOs yet')}
711 ${_('No TODOs yet')}
712 </td>
712 </td>
713 </tr>
713 </tr>
714 </table>
714 </table>
715 % endif
715 % endif
716 % endif
716 % endif
717 </div>
717 </div>
718 </div>
718 </div>
719
719
720 ## COMMENTS
720 ## COMMENTS
721 <div class="sidebar-element clear-both">
721 <div class="sidebar-element clear-both">
722 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
722 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
723 <i class="icon-comment" style="color: #949494"></i>
723 <i class="icon-comment" style="color: #949494"></i>
724 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
724 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
725 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
725 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
726 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
726 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
727 </div>
727 </div>
728
728
729 <div class="right-sidebar-expanded-state pr-details-title">
729 <div class="right-sidebar-expanded-state pr-details-title">
730 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
730 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
731 <i class="icon-comment" style="color: #949494"></i>
731 <i class="icon-comment" style="color: #949494"></i>
732 ${_('Comments')}
732 ${_('Comments')}
733
733
734 ## % if outdated_comm_count_ver:
734 ## % if outdated_comm_count_ver:
735 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
735 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
736 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
736 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
737 ## </a>
737 ## </a>
738 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
738 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
739 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
739 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
740
740
741 ## % else:
741 ## % else:
742 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
742 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
743 ## % endif
743 ## % endif
744
744
745 </span>
745 </span>
746
746
747 % if outdated_comm_count_ver:
747 % if outdated_comm_count_ver:
748 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
748 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
749 % else:
749 % else:
750 <span class="block-right last-item noselect">Show hidden</span>
750 <span class="block-right last-item noselect">Show hidden</span>
751 % endif
751 % endif
752
752
753 </div>
753 </div>
754
754
755 <div class="right-sidebar-expanded-state pr-details-content">
755 <div class="right-sidebar-expanded-state pr-details-content">
756 % if c.inline_comments_flat + c.comments:
756 % if c.inline_comments_flat + c.comments:
757 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
757 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
758 % else:
758 % else:
759 <table>
759 <table>
760 <tr>
760 <tr>
761 <td>
761 <td>
762 ${_('No Comments yet')}
762 ${_('No Comments yet')}
763 </td>
763 </td>
764 </tr>
764 </tr>
765 </table>
765 </table>
766 % endif
766 % endif
767 </div>
767 </div>
768
768
769 </div>
769 </div>
770
770
771 ## Referenced Tickets
771 ## Referenced Tickets
772 <div class="sidebar-element clear-both">
772 <div class="sidebar-element clear-both">
773 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
773 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
774 <i class="icon-info-circled"></i>
774 <i class="icon-info-circled"></i>
775 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
775 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
776 </div>
776 </div>
777
777
778 <div class="right-sidebar-expanded-state pr-details-title">
778 <div class="right-sidebar-expanded-state pr-details-title">
779 <span class="sidebar-heading">
779 <span class="sidebar-heading">
780 <i class="icon-info-circled"></i>
780 <i class="icon-info-circled"></i>
781 ${_('Referenced Tickets')}
781 ${_('Referenced Tickets')}
782 </span>
782 </span>
783 </div>
783 </div>
784 <div class="right-sidebar-expanded-state pr-details-content">
784 <div class="right-sidebar-expanded-state pr-details-content">
785 <table>
785 <table>
786
786
787 <tr><td><code>${_('In pull request description')}:</code></td></tr>
787 <tr><td><code>${_('In pull request description')}:</code></td></tr>
788 % if c.referenced_desc_issues:
788 % if c.referenced_desc_issues:
789 % for ticket_dict in c.referenced_desc_issues:
789 % for ticket_dict in c.referenced_desc_issues:
790 <tr>
790 <tr>
791 <td>
791 <td>
792 <a href="${ticket_dict.get('url')}">
792 <a href="${ticket_dict.get('url')}">
793 ${ticket_dict.get('id')}
793 ${ticket_dict.get('id')}
794 </a>
794 </a>
795 </td>
795 </td>
796 </tr>
796 </tr>
797 % endfor
797 % endfor
798 % else:
798 % else:
799 <tr>
799 <tr>
800 <td>
800 <td>
801 ${_('No Ticket data found.')}
801 ${_('No Ticket data found.')}
802 </td>
802 </td>
803 </tr>
803 </tr>
804 % endif
804 % endif
805
805
806 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
806 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
807 % if c.referenced_commit_issues:
807 % if c.referenced_commit_issues:
808 % for ticket_dict in c.referenced_commit_issues:
808 % for ticket_dict in c.referenced_commit_issues:
809 <tr>
809 <tr>
810 <td>
810 <td>
811 <a href="${ticket_dict.get('url')}">
811 <a href="${ticket_dict.get('url')}">
812 ${ticket_dict.get('id')}
812 ${ticket_dict.get('id')}
813 </a>
813 </a>
814 </td>
814 </td>
815 </tr>
815 </tr>
816 % endfor
816 % endfor
817 % else:
817 % else:
818 <tr>
818 <tr>
819 <td>
819 <td>
820 ${_('No Ticket data found.')}
820 ${_('No Ticket data found.')}
821 </td>
821 </td>
822 </tr>
822 </tr>
823 % endif
823 % endif
824 </table>
824 </table>
825
825
826 </div>
826 </div>
827 </div>
827 </div>
828
828
829 </div>
829 </div>
830
830
831 </div>
831 </div>
832 </aside>
832 </aside>
833
833
834 ## This JS needs to be at the end
834 ## This JS needs to be at the end
835 <script type="text/javascript">
835 <script type="text/javascript">
836
836
837 versionController = new VersionController();
837 versionController = new VersionController();
838 versionController.init();
838 versionController.init();
839
839
840 reviewersController = new ReviewersController();
840 reviewersController = new ReviewersController();
841 commitsController = new CommitsController();
841 commitsController = new CommitsController();
842
842
843 updateController = new UpdatePrController();
843 updateController = new UpdatePrController();
844
844
845 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
845 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
846 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
846 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
847 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
847 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
848
848
849 (function () {
849 (function () {
850 "use strict";
850 "use strict";
851
851
852 // custom code mirror
852 // custom code mirror
853 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
853 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
854
854
855 PRDetails.init();
855 PRDetails.init();
856 ReviewersPanel.init(reviewerRulesData, setReviewersData);
856 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
857 ObserversPanel.init(reviewerRulesData, setObserversData);
857 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
858
858
859 window.showOutdated = function (self) {
859 window.showOutdated = function (self) {
860 $('.comment-inline.comment-outdated').show();
860 $('.comment-inline.comment-outdated').show();
861 $('.filediff-outdated').show();
861 $('.filediff-outdated').show();
862 $('.showOutdatedComments').hide();
862 $('.showOutdatedComments').hide();
863 $('.hideOutdatedComments').show();
863 $('.hideOutdatedComments').show();
864 };
864 };
865
865
866 window.hideOutdated = function (self) {
866 window.hideOutdated = function (self) {
867 $('.comment-inline.comment-outdated').hide();
867 $('.comment-inline.comment-outdated').hide();
868 $('.filediff-outdated').hide();
868 $('.filediff-outdated').hide();
869 $('.hideOutdatedComments').hide();
869 $('.hideOutdatedComments').hide();
870 $('.showOutdatedComments').show();
870 $('.showOutdatedComments').show();
871 };
871 };
872
872
873 window.refreshMergeChecks = function () {
873 window.refreshMergeChecks = function () {
874 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
874 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
875 $('.pull-request-merge').css('opacity', 0.3);
875 $('.pull-request-merge').css('opacity', 0.3);
876 $('.action-buttons-extra').css('opacity', 0.3);
876 $('.action-buttons-extra').css('opacity', 0.3);
877
877
878 $('.pull-request-merge').load(
878 $('.pull-request-merge').load(
879 loadUrl, function () {
879 loadUrl, function () {
880 $('.pull-request-merge').css('opacity', 1);
880 $('.pull-request-merge').css('opacity', 1);
881
881
882 $('.action-buttons-extra').css('opacity', 1);
882 $('.action-buttons-extra').css('opacity', 1);
883 }
883 }
884 );
884 );
885 };
885 };
886
886
887 window.closePullRequest = function (status) {
887 window.closePullRequest = function (status) {
888 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
888 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
889 return false;
889 return false;
890 }
890 }
891 // inject closing flag
891 // inject closing flag
892 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
892 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
893 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
893 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
894 $(generalCommentForm.submitForm).submit();
894 $(generalCommentForm.submitForm).submit();
895 };
895 };
896
896
897 //TODO this functionality is now missing
897 //TODO this functionality is now missing
898 $('#show-outdated-comments').on('click', function (e) {
898 $('#show-outdated-comments').on('click', function (e) {
899 var button = $(this);
899 var button = $(this);
900 var outdated = $('.comment-outdated');
900 var outdated = $('.comment-outdated');
901
901
902 if (button.html() === "(Show)") {
902 if (button.html() === "(Show)") {
903 button.html("(Hide)");
903 button.html("(Hide)");
904 outdated.show();
904 outdated.show();
905 } else {
905 } else {
906 button.html("(Show)");
906 button.html("(Show)");
907 outdated.hide();
907 outdated.hide();
908 }
908 }
909 });
909 });
910
910
911 $('#merge_pull_request_form').submit(function () {
911 $('#merge_pull_request_form').submit(function () {
912 if (!$('#merge_pull_request').attr('disabled')) {
912 if (!$('#merge_pull_request').attr('disabled')) {
913 $('#merge_pull_request').attr('disabled', 'disabled');
913 $('#merge_pull_request').attr('disabled', 'disabled');
914 }
914 }
915 return true;
915 return true;
916 });
916 });
917
917
918 $('#edit_pull_request').on('click', function (e) {
918 $('#edit_pull_request').on('click', function (e) {
919 var title = $('#pr-title-input').val();
919 var title = $('#pr-title-input').val();
920 var description = codeMirrorInstance.getValue();
920 var description = codeMirrorInstance.getValue();
921 var renderer = $('#pr-renderer-input').val();
921 var renderer = $('#pr-renderer-input').val();
922 editPullRequest(
922 editPullRequest(
923 "${c.repo_name}", "${c.pull_request.pull_request_id}",
923 "${c.repo_name}", "${c.pull_request.pull_request_id}",
924 title, description, renderer);
924 title, description, renderer);
925 });
925 });
926
926
927 var $updateButtons = $('#update_reviewers,#update_observers');
927 var $updateButtons = $('#update_reviewers,#update_observers');
928 $updateButtons.on('click', function (e) {
928 $updateButtons.on('click', function (e) {
929 var role = $(this).data('role');
929 var role = $(this).data('role');
930 $updateButtons.attr('disabled', 'disabled');
930 $updateButtons.attr('disabled', 'disabled');
931 $updateButtons.addClass('disabled');
931 $updateButtons.addClass('disabled');
932 $updateButtons.html(_gettext('Saving...'));
932 $updateButtons.html(_gettext('Saving...'));
933 reviewersController.updateReviewers(
933 reviewersController.updateReviewers(
934 templateContext.repo_name,
934 templateContext.repo_name,
935 templateContext.pull_request_data.pull_request_id,
935 templateContext.pull_request_data.pull_request_id,
936 role
936 role
937 );
937 );
938 });
938 });
939
939
940 // fixing issue with caches on firefox
940 // fixing issue with caches on firefox
941 $('#update_commits').removeAttr("disabled");
941 $('#update_commits').removeAttr("disabled");
942
942
943 $('.show-inline-comments').on('click', function (e) {
943 $('.show-inline-comments').on('click', function (e) {
944 var boxid = $(this).attr('data-comment-id');
944 var boxid = $(this).attr('data-comment-id');
945 var button = $(this);
945 var button = $(this);
946
946
947 if (button.hasClass("comments-visible")) {
947 if (button.hasClass("comments-visible")) {
948 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
948 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
949 $(this).hide();
949 $(this).hide();
950 });
950 });
951 button.removeClass("comments-visible");
951 button.removeClass("comments-visible");
952 } else {
952 } else {
953 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
953 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
954 $(this).show();
954 $(this).show();
955 });
955 });
956 button.addClass("comments-visible");
956 button.addClass("comments-visible");
957 }
957 }
958 });
958 });
959
959
960 $('.show-inline-comments').on('change', function (e) {
960 $('.show-inline-comments').on('change', function (e) {
961 var show = 'none';
961 var show = 'none';
962 var target = e.currentTarget;
962 var target = e.currentTarget;
963 if (target.checked) {
963 if (target.checked) {
964 show = ''
964 show = ''
965 }
965 }
966 var boxid = $(target).attr('id_for');
966 var boxid = $(target).attr('id_for');
967 var comments = $('#{0} .inline-comments'.format(boxid));
967 var comments = $('#{0} .inline-comments'.format(boxid));
968 var fn_display = function (idx) {
968 var fn_display = function (idx) {
969 $(this).css('display', show);
969 $(this).css('display', show);
970 };
970 };
971 $(comments).each(fn_display);
971 $(comments).each(fn_display);
972 var btns = $('#{0} .inline-comments-button'.format(boxid));
972 var btns = $('#{0} .inline-comments-button'.format(boxid));
973 $(btns).each(fn_display);
973 $(btns).each(fn_display);
974 });
974 });
975
975
976 // register submit callback on commentForm form to track TODOs
976 // register submit callback on commentForm form to track TODOs
977 window.commentFormGlobalSubmitSuccessCallback = function () {
977 window.commentFormGlobalSubmitSuccessCallback = function () {
978 refreshMergeChecks();
978 refreshMergeChecks();
979 };
979 };
980
980
981 ReviewerAutoComplete('#user', reviewersController);
981 ReviewerAutoComplete('#user', reviewersController);
982 ObserverAutoComplete('#observer', reviewersController);
982 ObserverAutoComplete('#observer', reviewersController);
983
983
984 })();
984 })();
985
985
986 $(document).ready(function () {
986 $(document).ready(function () {
987
987
988 var channel = '${c.pr_broadcast_channel}';
988 var channel = '${c.pr_broadcast_channel}';
989 new ReviewerPresenceController(channel)
989 new ReviewerPresenceController(channel)
990
990
991 })
991 })
992 </script>
992 </script>
993
993
994 </%def>
994 </%def>
General Comments 0
You need to be logged in to leave comments. Login now