##// END OF EJS Templates
default-reviewers: fixed edit mode when creating new entries.
marcink -
r2504:dc37941f default
parent child Browse files
Show More
@@ -1,586 +1,587 b''
1 1 // # Copyright (C) 2010-2018 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 var prButtonLockChecks = {
21 21 'compare': false,
22 22 'reviewers': false
23 23 };
24 24
25 25 /**
26 26 * lock button until all checks and loads are made. E.g reviewer calculation
27 27 * should prevent from submitting a PR
28 28 * @param lockEnabled
29 29 * @param msg
30 30 * @param scope
31 31 */
32 32 var prButtonLock = function(lockEnabled, msg, scope) {
33 33 scope = scope || 'all';
34 34 if (scope == 'all'){
35 35 prButtonLockChecks['compare'] = !lockEnabled;
36 36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 37 } else if (scope == 'compare') {
38 38 prButtonLockChecks['compare'] = !lockEnabled;
39 39 } else if (scope == 'reviewers'){
40 40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 41 }
42 42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 43 if (lockEnabled) {
44 44 $('#save').attr('disabled', 'disabled');
45 45 }
46 46 else if (checksMeet) {
47 47 $('#save').removeAttr('disabled');
48 48 }
49 49
50 50 if (msg) {
51 51 $('#pr_open_message').html(msg);
52 52 }
53 53 };
54 54
55 55
56 56 /**
57 57 Generate Title and Description for a PullRequest.
58 58 In case of 1 commits, the title and description is that one commit
59 59 in case of multiple commits, we iterate on them with max N number of commits,
60 60 and build description in a form
61 61 - commitN
62 62 - commitN+1
63 63 ...
64 64
65 65 Title is then constructed from branch names, or other references,
66 66 replacing '-' and '_' into spaces
67 67
68 68 * @param sourceRef
69 69 * @param elements
70 70 * @param limit
71 71 * @returns {*[]}
72 72 */
73 73 var getTitleAndDescription = function(sourceRef, elements, limit) {
74 74 var title = '';
75 75 var desc = '';
76 76
77 77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
79 79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 80 });
81 81 // only 1 commit, use commit message as title
82 82 if (elements.length === 1) {
83 83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 84 }
85 85 else {
86 86 // use reference name
87 87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 88 }
89 89
90 90 return [title, desc]
91 91 };
92 92
93 93
94 94
95 95 ReviewersController = function () {
96 96 var self = this;
97 97 this.$reviewRulesContainer = $('#review_rules');
98 98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 99 this.forbidReviewUsers = undefined;
100 100 this.$reviewMembers = $('#review_members');
101 101 this.currentRequest = null;
102 102
103 103 this.defaultForbidReviewUsers = function() {
104 104 return [
105 105 {'username': 'default',
106 106 'user_id': templateContext.default_user.user_id}
107 107 ];
108 108 };
109 109
110 110 this.hideReviewRules = function() {
111 111 self.$reviewRulesContainer.hide();
112 112 };
113 113
114 114 this.showReviewRules = function() {
115 115 self.$reviewRulesContainer.show();
116 116 };
117 117
118 118 this.addRule = function(ruleText) {
119 119 self.showReviewRules();
120 120 return '<div>- {0}</div>'.format(ruleText)
121 121 };
122 122
123 123 this.loadReviewRules = function(data) {
124 124 // reset forbidden Users
125 125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126 126
127 127 // reset state of review rules
128 128 self.$rulesList.html('');
129 129
130 130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 131 // default rule, case for older repo that don't have any rules stored
132 132 self.$rulesList.append(
133 133 self.addRule(
134 134 _gettext('All reviewers must vote.'))
135 135 );
136 136 return self.forbidReviewUsers
137 137 }
138 138
139 139 if (data.rules.voting !== undefined) {
140 140 if (data.rules.voting < 0) {
141 141 self.$rulesList.append(
142 142 self.addRule(
143 143 _gettext('All individual reviewers must vote.'))
144 144 )
145 145 } else if (data.rules.voting === 1) {
146 146 self.$rulesList.append(
147 147 self.addRule(
148 148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 149 )
150 150
151 151 } else {
152 152 self.$rulesList.append(
153 153 self.addRule(
154 154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 155 )
156 156 }
157 157 }
158 158
159 159 if (data.rules.voting_groups !== undefined) {
160 160 $.each(data.rules.voting_groups, function(index, rule_data) {
161 161 self.$rulesList.append(
162 162 self.addRule(rule_data.text)
163 163 )
164 164 });
165 165 }
166 166
167 167 if (data.rules.use_code_authors_for_review) {
168 168 self.$rulesList.append(
169 169 self.addRule(
170 170 _gettext('Reviewers picked from source code changes.'))
171 171 )
172 172 }
173 173 if (data.rules.forbid_adding_reviewers) {
174 174 $('#add_reviewer_input').remove();
175 175 self.$rulesList.append(
176 176 self.addRule(
177 177 _gettext('Adding new reviewers is forbidden.'))
178 178 )
179 179 }
180 180 if (data.rules.forbid_author_to_review) {
181 181 self.forbidReviewUsers.push(data.rules_data.pr_author);
182 182 self.$rulesList.append(
183 183 self.addRule(
184 184 _gettext('Author is not allowed to be a reviewer.'))
185 185 )
186 186 }
187 187 if (data.rules.forbid_commit_author_to_review) {
188 188
189 189 if (data.rules_data.forbidden_users) {
190 190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
191 191 self.forbidReviewUsers.push(member_data)
192 192 });
193 193
194 194 }
195 195
196 196 self.$rulesList.append(
197 197 self.addRule(
198 198 _gettext('Commit Authors are not allowed to be a reviewer.'))
199 199 )
200 200 }
201 201
202 202 return self.forbidReviewUsers
203 203 };
204 204
205 205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
206 206
207 207 if (self.currentRequest) {
208 208 // make sure we cleanup old running requests before triggering this
209 209 // again
210 210 self.currentRequest.abort();
211 211 }
212 212
213 213 $('.calculate-reviewers').show();
214 214 // reset reviewer members
215 215 self.$reviewMembers.empty();
216 216
217 217 prButtonLock(true, null, 'reviewers');
218 218 $('#user').hide(); // hide user autocomplete before load
219 219
220 220 var url = pyroutes.url('repo_default_reviewers_data',
221 221 {
222 222 'repo_name': templateContext.repo_name,
223 223 'source_repo': sourceRepo,
224 224 'source_ref': sourceRef[2],
225 225 'target_repo': targetRepo,
226 226 'target_ref': targetRef[2]
227 227 });
228 228
229 229 self.currentRequest = $.get(url)
230 230 .done(function(data) {
231 231 self.currentRequest = null;
232 232
233 233 // review rules
234 234 self.loadReviewRules(data);
235 235
236 236 for (var i = 0; i < data.reviewers.length; i++) {
237 237 var reviewer = data.reviewers[i];
238 238 self.addReviewMember(
239 239 reviewer, reviewer.reasons, reviewer.mandatory);
240 240 }
241 241 $('.calculate-reviewers').hide();
242 242 prButtonLock(false, null, 'reviewers');
243 243 $('#user').show(); // show user autocomplete after load
244 244 });
245 245 };
246 246
247 247 // check those, refactor
248 248 this.removeReviewMember = function(reviewer_id, mark_delete) {
249 249 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
250 250
251 251 if(typeof(mark_delete) === undefined){
252 252 mark_delete = false;
253 253 }
254 254
255 255 if(mark_delete === true){
256 256 if (reviewer){
257 257 // now delete the input
258 258 $('#reviewer_{0} input'.format(reviewer_id)).remove();
259 259 // mark as to-delete
260 260 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
261 261 obj.addClass('to-delete');
262 262 obj.css({"text-decoration":"line-through", "opacity": 0.5});
263 263 }
264 264 }
265 265 else{
266 266 $('#reviewer_{0}'.format(reviewer_id)).remove();
267 267 }
268 268 };
269 269 this.reviewMemberEntry = function() {
270 270
271 271 };
272 272 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
273 273 var members = self.$reviewMembers.get(0);
274 274 var id = reviewer_obj.user_id;
275 275 var username = reviewer_obj.username;
276 276
277 277 var reasons = reasons || [];
278 278 var mandatory = mandatory || false;
279 279
280 280 // register IDS to check if we don't have this ID already in
281 281 var currentIds = [];
282 282 var _els = self.$reviewMembers.find('li').toArray();
283 283 for (el in _els){
284 284 currentIds.push(_els[el].id)
285 285 }
286 286
287 287 var userAllowedReview = function(userId) {
288 288 var allowed = true;
289 289 $.each(self.forbidReviewUsers, function(index, member_data) {
290 290 if (parseInt(userId) === member_data['user_id']) {
291 291 allowed = false;
292 292 return false // breaks the loop
293 293 }
294 294 });
295 295 return allowed
296 296 };
297 297
298 298 var userAllowed = userAllowedReview(id);
299 299 if (!userAllowed){
300 300 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
301 301 } else {
302 302 // only add if it's not there
303 303 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
304 304
305 305 if (alreadyReviewer) {
306 306 alert(_gettext('User `{0}` already in reviewers').format(username));
307 307 } else {
308 308 members.innerHTML += renderTemplate('reviewMemberEntry', {
309 309 'member': reviewer_obj,
310 310 'mandatory': mandatory,
311 311 'allowed_to_update': true,
312 312 'review_status': 'not_reviewed',
313 313 'review_status_label': _gettext('Not Reviewed'),
314 'reasons': reasons
314 'reasons': reasons,
315 'create': true
315 316 });
316 317 }
317 318 }
318 319
319 320 };
320 321
321 322 this.updateReviewers = function(repo_name, pull_request_id){
322 323 var postData = $('#reviewers input').serialize();
323 324 _updatePullRequest(repo_name, pull_request_id, postData);
324 325 };
325 326
326 327 };
327 328
328 329
329 330 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
330 331 var url = pyroutes.url(
331 332 'pullrequest_update',
332 333 {"repo_name": repo_name, "pull_request_id": pull_request_id});
333 334 if (typeof postData === 'string' ) {
334 335 postData += '&csrf_token=' + CSRF_TOKEN;
335 336 } else {
336 337 postData.csrf_token = CSRF_TOKEN;
337 338 }
338 339 var success = function(o) {
339 340 window.location.reload();
340 341 };
341 342 ajaxPOST(url, postData, success);
342 343 };
343 344
344 345 /**
345 346 * PULL REQUEST update commits
346 347 */
347 348 var updateCommits = function(repo_name, pull_request_id) {
348 349 var postData = {
349 350 'update_commits': true};
350 351 _updatePullRequest(repo_name, pull_request_id, postData);
351 352 };
352 353
353 354
354 355 /**
355 356 * PULL REQUEST edit info
356 357 */
357 358 var editPullRequest = function(repo_name, pull_request_id, title, description) {
358 359 var url = pyroutes.url(
359 360 'pullrequest_update',
360 361 {"repo_name": repo_name, "pull_request_id": pull_request_id});
361 362
362 363 var postData = {
363 364 'title': title,
364 365 'description': description,
365 366 'edit_pull_request': true,
366 367 'csrf_token': CSRF_TOKEN
367 368 };
368 369 var success = function(o) {
369 370 window.location.reload();
370 371 };
371 372 ajaxPOST(url, postData, success);
372 373 };
373 374
374 375 var initPullRequestsCodeMirror = function (textAreaId) {
375 376 var ta = $(textAreaId).get(0);
376 377 var initialHeight = '100px';
377 378
378 379 // default options
379 380 var codeMirrorOptions = {
380 381 mode: "text",
381 382 lineNumbers: false,
382 383 indentUnit: 4,
383 384 theme: 'rc-input'
384 385 };
385 386
386 387 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
387 388 // marker for manually set description
388 389 codeMirrorInstance._userDefinedDesc = false;
389 390 codeMirrorInstance.setSize(null, initialHeight);
390 391 codeMirrorInstance.on("change", function(instance, changeObj) {
391 392 var height = initialHeight;
392 393 var lines = instance.lineCount();
393 394 if (lines > 6 && lines < 20) {
394 395 height = "auto"
395 396 }
396 397 else if (lines >= 20) {
397 398 height = 20 * 15;
398 399 }
399 400 instance.setSize(null, height);
400 401
401 402 // detect if the change was trigger by auto desc, or user input
402 403 changeOrigin = changeObj.origin;
403 404
404 405 if (changeOrigin === "setValue") {
405 406 cmLog.debug('Change triggered by setValue');
406 407 }
407 408 else {
408 409 cmLog.debug('user triggered change !');
409 410 // set special marker to indicate user has created an input.
410 411 instance._userDefinedDesc = true;
411 412 }
412 413
413 414 });
414 415
415 416 return codeMirrorInstance
416 417 };
417 418
418 419 /**
419 420 * Reviewer autocomplete
420 421 */
421 422 var ReviewerAutoComplete = function(inputId) {
422 423 $(inputId).autocomplete({
423 424 serviceUrl: pyroutes.url('user_autocomplete_data'),
424 425 minChars:2,
425 426 maxHeight:400,
426 427 deferRequestBy: 300, //miliseconds
427 428 showNoSuggestionNotice: true,
428 429 tabDisabled: true,
429 430 autoSelectFirst: true,
430 431 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
431 432 formatResult: autocompleteFormatResult,
432 433 lookupFilter: autocompleteFilterResult,
433 434 onSelect: function(element, data) {
434 435 var mandatory = false;
435 436 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
436 437
437 438 // add whole user groups
438 439 if (data.value_type == 'user_group') {
439 440 reasons.push(_gettext('member of "{0}"').format(data.value_display));
440 441
441 442 $.each(data.members, function(index, member_data) {
442 443 var reviewer = member_data;
443 444 reviewer['user_id'] = member_data['id'];
444 445 reviewer['gravatar_link'] = member_data['icon_link'];
445 446 reviewer['user_link'] = member_data['profile_link'];
446 447 reviewer['rules'] = [];
447 448 reviewersController.addReviewMember(reviewer, reasons, mandatory);
448 449 })
449 450 }
450 451 // add single user
451 452 else {
452 453 var reviewer = data;
453 454 reviewer['user_id'] = data['id'];
454 455 reviewer['gravatar_link'] = data['icon_link'];
455 456 reviewer['user_link'] = data['profile_link'];
456 457 reviewer['rules'] = [];
457 458 reviewersController.addReviewMember(reviewer, reasons, mandatory);
458 459 }
459 460
460 461 $(inputId).val('');
461 462 }
462 463 });
463 464 };
464 465
465 466
466 467 VersionController = function () {
467 468 var self = this;
468 469 this.$verSource = $('input[name=ver_source]');
469 470 this.$verTarget = $('input[name=ver_target]');
470 471 this.$showVersionDiff = $('#show-version-diff');
471 472
472 473 this.adjustRadioSelectors = function (curNode) {
473 474 var getVal = function (item) {
474 475 if (item == 'latest') {
475 476 return Number.MAX_SAFE_INTEGER
476 477 }
477 478 else {
478 479 return parseInt(item)
479 480 }
480 481 };
481 482
482 483 var curVal = getVal($(curNode).val());
483 484 var cleared = false;
484 485
485 486 $.each(self.$verSource, function (index, value) {
486 487 var elVal = getVal($(value).val());
487 488
488 489 if (elVal > curVal) {
489 490 if ($(value).is(':checked')) {
490 491 cleared = true;
491 492 }
492 493 $(value).attr('disabled', 'disabled');
493 494 $(value).removeAttr('checked');
494 495 $(value).css({'opacity': 0.1});
495 496 }
496 497 else {
497 498 $(value).css({'opacity': 1});
498 499 $(value).removeAttr('disabled');
499 500 }
500 501 });
501 502
502 503 if (cleared) {
503 504 // if we unchecked an active, set the next one to same loc.
504 505 $(this.$verSource).filter('[value={0}]'.format(
505 506 curVal)).attr('checked', 'checked');
506 507 }
507 508
508 509 self.setLockAction(false,
509 510 $(curNode).data('verPos'),
510 511 $(this.$verSource).filter(':checked').data('verPos')
511 512 );
512 513 };
513 514
514 515
515 516 this.attachVersionListener = function () {
516 517 self.$verTarget.change(function (e) {
517 518 self.adjustRadioSelectors(this)
518 519 });
519 520 self.$verSource.change(function (e) {
520 521 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
521 522 });
522 523 };
523 524
524 525 this.init = function () {
525 526
526 527 var curNode = self.$verTarget.filter(':checked');
527 528 self.adjustRadioSelectors(curNode);
528 529 self.setLockAction(true);
529 530 self.attachVersionListener();
530 531
531 532 };
532 533
533 534 this.setLockAction = function (state, selectedVersion, otherVersion) {
534 535 var $showVersionDiff = this.$showVersionDiff;
535 536
536 537 if (state) {
537 538 $showVersionDiff.attr('disabled', 'disabled');
538 539 $showVersionDiff.addClass('disabled');
539 540 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
540 541 }
541 542 else {
542 543 $showVersionDiff.removeAttr('disabled');
543 544 $showVersionDiff.removeClass('disabled');
544 545
545 546 if (selectedVersion == otherVersion) {
546 547 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
547 548 } else {
548 549 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
549 550 }
550 551 }
551 552
552 553 };
553 554
554 555 this.showVersionDiff = function () {
555 556 var target = self.$verTarget.filter(':checked');
556 557 var source = self.$verSource.filter(':checked');
557 558
558 559 if (target.val() && source.val()) {
559 560 var params = {
560 561 'pull_request_id': templateContext.pull_request_data.pull_request_id,
561 562 'repo_name': templateContext.repo_name,
562 563 'version': target.val(),
563 564 'from_version': source.val()
564 565 };
565 566 window.location = pyroutes.url('pullrequest_show', params)
566 567 }
567 568
568 569 return false;
569 570 };
570 571
571 572 this.toggleVersionView = function (elem) {
572 573
573 574 if (this.$showVersionDiff.is(':visible')) {
574 575 $('.version-pr').hide();
575 576 this.$showVersionDiff.hide();
576 577 $(elem).html($(elem).data('toggleOn'))
577 578 } else {
578 579 $('.version-pr').show();
579 580 this.$showVersionDiff.show();
580 581 $(elem).html($(elem).data('toggleOff'))
581 582 }
582 583
583 584 return false
584 585 }
585 586
586 587 }; No newline at end of file
@@ -1,125 +1,129 b''
1 1 <%text>
2 2 <div style="display: none">
3 3
4 4 <script id="ejs_gravatarWithUser" type="text/template" class="ejsTemplate">
5 5
6 6 <%
7 7 if (size > 16) {
8 8 var gravatar_class = 'gravatar gravatar-large';
9 9 } else {
10 10 var gravatar_class = 'gravatar';
11 11 }
12 12 %>
13 13
14 14 <%
15 15 if (show_disabled) {
16 16 var user_cls = 'user user-disabled';
17 17 } else {
18 18 var user_cls = 'user';
19 19 }
20 20 %>
21 21
22 22 <div class="rc-user">
23 23 <img class="<%= gravatar_class %>" src="<%- gravatar_url -%>" height="<%= size %>" width="<%= size %>">
24 24 <span class="<%= user_cls %>"> <%- user_link -%> </span>
25 25 </div>
26 26
27 27 </script>
28 28
29 29 <script>
30 30 var CG = new ColorGenerator();
31 31 </script>
32 32
33 33 <script id="ejs_reviewMemberEntry" type="text/template" class="ejsTemplate">
34 34
35 35 <li id="reviewer_<%= member.user_id %>" class="reviewer_entry">
36 36 <%
37 if (create) {
38 var edit_visibility = 'visible';
39 } else {
40 var edit_visibility = 'hidden';
41 }
37 42
38 43 if (member.user_group && member.user_group.vote_rule) {
39 44 var groupStyle = 'border-left: 1px solid '+CG.asRGB(CG.getColor(member.user_group.vote_rule));
40 45 } else {
41 46 var groupStyle = 'border-left: 1px solid white';
42 47 }
43
44 48 %>
45 49
46 50 <div class="reviewers_member" style="<%= groupStyle%>" >
47 51 <div class="reviewer_status tooltip" title="<%= review_status_label %>">
48 52 <div class="flag_status <%= review_status %> pull-left reviewer_member_status"></div>
49 53 </div>
50 54 <div id="reviewer_<%= member.user_id %>_name" class="reviewer_name">
51 55 <% if (mandatory) { %>
52 56 <div class="reviewer_member_mandatory tooltip" title="Mandatory reviewer">
53 57 <i class="icon-lock"></i>
54 58 </div>
55 59 <% } %>
56 60
57 61 <%-
58 62 renderTemplate('gravatarWithUser', {
59 63 'size': 16,
60 64 'show_disabled': false,
61 65 'user_link': member.user_link,
62 66 'gravatar_url': member.gravatar_link
63 67 })
64 68 %>
65 69 </div>
66 70
67 71 <input type="hidden" name="__start__" value="reviewer:mapping">
68 72
69 73
70 74 <%if (member.user_group && member.user_group.vote_rule) {%>
71 75 <div class="reviewer_reason">
72 76
73 77 <%if (member.user_group.vote_rule == -1) {%>
74 78 - group votes required: ALL
75 79 <%} else {%>
76 80 - group votes required: <%= member.user_group.vote_rule %>
77 81 <%}%>
78 82 </div>
79 83 <%}%>
80 84
81 85 <input type="hidden" name="__start__" value="reasons:sequence">
82 86 <% for (var i = 0; i < reasons.length; i++) { %>
83 87 <% var reason = reasons[i] %>
84 88 <div class="reviewer_reason">- <%= reason %></div>
85 89 <input type="hidden" name="reason" value="<%= reason %>">
86 90 <% } %>
87 91 <input type="hidden" name="__end__" value="reasons:sequence">
88 92
89 93 <input type="hidden" name="__start__" value="rules:sequence">
90 94 <% for (var i = 0; i < member.rules.length; i++) { %>
91 95 <% var rule = member.rules[i] %>
92 96 <input type="hidden" name="rule_id" value="<%= rule %>">
93 97 <% } %>
94 98 <input type="hidden" name="__end__" value="rules:sequence">
95 99
96 100 <input id="reviewer_<%= member.user_id %>_input" type="hidden" value="<%= member.user_id %>" name="user_id" />
97 101 <input type="hidden" name="mandatory" value="<%= mandatory %>"/>
98 102
99 103 <input type="hidden" name="__end__" value="reviewer:mapping">
100 104
101 105 <% if (mandatory) { %>
102 <div class="reviewer_member_mandatory_remove" style="visibility: hidden;">
106 <div class="reviewer_member_mandatory_remove" style="visibility: <%= edit_visibility %>;">
103 107 <i class="icon-remove-sign"></i>
104 108 </div>
105 109 <% } else { %>
106 110 <% if (allowed_to_update) { %>
107 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: hidden;">
111 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
108 112 <i class="icon-remove-sign" ></i>
109 113 </div>
110 114 <% } %>
111 115 <% } %>
112 116 </div>
113 117 </li>
114 118
115 119 </script>
116 120
117 121
118 122 </div>
119 123
120 124 <script>
121 125 // registers the templates into global cache
122 126 registerTemplates();
123 127 </script>
124 128
125 129 </%text>
@@ -1,853 +1,854 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <span id="pr-title">
13 13 ${c.pull_request.title}
14 14 %if c.pull_request.is_closed():
15 15 (${_('Closed')})
16 16 %endif
17 17 </span>
18 18 <div id="pr-title-edit" class="input" style="display: none;">
19 19 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 20 </div>
21 21 </%def>
22 22
23 23 <%def name="menu_bar_nav()">
24 24 ${self.menu_items(active='repositories')}
25 25 </%def>
26 26
27 27 <%def name="menu_bar_subnav()">
28 28 ${self.repo_menu(active='showpullrequest')}
29 29 </%def>
30 30
31 31 <%def name="main()">
32 32
33 33 <script type="text/javascript">
34 34 // TODO: marcink switch this to pyroutes
35 35 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 36 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 37 </script>
38 38 <div class="box">
39 39
40 40 <div class="title">
41 41 ${self.repo_page_title(c.rhodecode_db_repo)}
42 42 </div>
43 43
44 44 ${self.breadcrumbs()}
45 45
46 46 <div class="box pr-summary">
47 47
48 48 <div class="summary-details block-left">
49 49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 50 <div class="pr-details-title">
51 51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 52 %if c.allowed_to_update:
53 53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 54 % if c.allowed_to_delete:
55 55 ${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)}
56 56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 58 ${h.end_form()}
59 59 % else:
60 60 ${_('Delete')}
61 61 % endif
62 62 </div>
63 63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 65 %endif
66 66 </div>
67 67
68 68 <div id="summary" class="fields pr-details-content">
69 69 <div class="field">
70 70 <div class="label-summary">
71 71 <label>${_('Source')}:</label>
72 72 </div>
73 73 <div class="input">
74 74 <div class="pr-origininfo">
75 75 ## branch link is only valid if it is a branch
76 76 <span class="tag">
77 77 %if c.pull_request.source_ref_parts.type == 'branch':
78 78 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
79 79 %else:
80 80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 81 %endif
82 82 </span>
83 83 <span class="clone-url">
84 84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 85 </span>
86 86 <br/>
87 87 % if c.ancestor_commit:
88 88 ${_('Common ancestor')}:
89 89 <code><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></code>
90 90 % endif
91 91 </div>
92 92 %if h.is_hg(c.pull_request.source_repo):
93 93 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
94 94 %elif h.is_git(c.pull_request.source_repo):
95 95 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
96 96 %endif
97 97
98 98 <div class="">
99 99 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
100 100 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
101 101 </div>
102 102
103 103 </div>
104 104 </div>
105 105 <div class="field">
106 106 <div class="label-summary">
107 107 <label>${_('Target')}:</label>
108 108 </div>
109 109 <div class="input">
110 110 <div class="pr-targetinfo">
111 111 ## branch link is only valid if it is a branch
112 112 <span class="tag">
113 113 %if c.pull_request.target_ref_parts.type == 'branch':
114 114 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
115 115 %else:
116 116 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
117 117 %endif
118 118 </span>
119 119 <span class="clone-url">
120 120 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
121 121 </span>
122 122 </div>
123 123 </div>
124 124 </div>
125 125
126 126 ## Link to the shadow repository.
127 127 <div class="field">
128 128 <div class="label-summary">
129 129 <label>${_('Merge')}:</label>
130 130 </div>
131 131 <div class="input">
132 132 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
133 133 %if h.is_hg(c.pull_request.target_repo):
134 134 <% 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) %>
135 135 %elif h.is_git(c.pull_request.target_repo):
136 136 <% 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) %>
137 137 %endif
138 138 <div class="">
139 139 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
140 140 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
141 141 </div>
142 142 % else:
143 143 <div class="">
144 144 ${_('Shadow repository data not available')}.
145 145 </div>
146 146 % endif
147 147 </div>
148 148 </div>
149 149
150 150 <div class="field">
151 151 <div class="label-summary">
152 152 <label>${_('Review')}:</label>
153 153 </div>
154 154 <div class="input">
155 155 %if c.pull_request_review_status:
156 156 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
157 157 <span class="changeset-status-lbl tooltip">
158 158 %if c.pull_request.is_closed():
159 159 ${_('Closed')},
160 160 %endif
161 161 ${h.commit_status_lbl(c.pull_request_review_status)}
162 162 </span>
163 163 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
164 164 %endif
165 165 </div>
166 166 </div>
167 167 <div class="field">
168 168 <div class="pr-description-label label-summary">
169 169 <label>${_('Description')}:</label>
170 170 </div>
171 171 <div id="pr-desc" class="input">
172 172 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
173 173 </div>
174 174 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
175 175 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
176 176 </div>
177 177 </div>
178 178
179 179 <div class="field">
180 180 <div class="label-summary">
181 181 <label>${_('Versions')}:</label>
182 182 </div>
183 183
184 184 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
185 185 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
186 186
187 187 <div class="pr-versions">
188 188 % if c.show_version_changes:
189 189 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
190 190 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
191 191 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
192 192 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
193 193 data-toggle-off="${_('Hide all versions of this pull request')}">
194 194 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
195 195 </a>
196 196 <table>
197 197 ## SHOW ALL VERSIONS OF PR
198 198 <% ver_pr = None %>
199 199
200 200 % for data in reversed(list(enumerate(c.versions, 1))):
201 201 <% ver_pos = data[0] %>
202 202 <% ver = data[1] %>
203 203 <% ver_pr = ver.pull_request_version_id %>
204 204 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
205 205
206 206 <tr class="version-pr" style="display: ${display_row}">
207 207 <td>
208 208 <code>
209 209 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
210 210 </code>
211 211 </td>
212 212 <td>
213 213 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
214 214 <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}"/>
215 215 </td>
216 216 <td>
217 217 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
218 218 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
219 219 </div>
220 220 </td>
221 221 <td>
222 222 % if c.at_version_num != ver_pr:
223 223 <i class="icon-comment"></i>
224 224 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
225 225 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
226 226 </code>
227 227 % endif
228 228 </td>
229 229 <td>
230 230 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
231 231 </td>
232 232 <td>
233 233 ${h.age_component(ver.updated_on, time_is_local=True)}
234 234 </td>
235 235 </tr>
236 236 % endfor
237 237
238 238 <tr>
239 239 <td colspan="6">
240 240 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
241 241 data-label-text-locked="${_('select versions to show changes')}"
242 242 data-label-text-diff="${_('show changes between versions')}"
243 243 data-label-text-show="${_('show pull request for this version')}"
244 244 >
245 245 ${_('select versions to show changes')}
246 246 </button>
247 247 </td>
248 248 </tr>
249 249
250 250 ## show comment/inline comments summary
251 251 <%def name="comments_summary()">
252 252 <tr>
253 253 <td colspan="6" class="comments-summary-td">
254 254
255 255 % if c.at_version:
256 256 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
257 257 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
258 258 ${_('Comments at this version')}:
259 259 % else:
260 260 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
261 261 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
262 262 ${_('Comments for this pull request')}:
263 263 % endif
264 264
265 265
266 266 %if general_comm_count_ver:
267 267 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
268 268 %else:
269 269 ${_("%d General ") % general_comm_count_ver}
270 270 %endif
271 271
272 272 %if inline_comm_count_ver:
273 273 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
274 274 %else:
275 275 , ${_("%d Inline") % inline_comm_count_ver}
276 276 %endif
277 277
278 278 %if outdated_comm_count_ver:
279 279 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
280 280 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
281 281 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
282 282 %else:
283 283 , ${_("%d Outdated") % outdated_comm_count_ver}
284 284 %endif
285 285 </td>
286 286 </tr>
287 287 </%def>
288 288 ${comments_summary()}
289 289 </table>
290 290 % else:
291 291 <div class="input">
292 292 ${_('Pull request versions not available')}.
293 293 </div>
294 294 <div>
295 295 <table>
296 296 ${comments_summary()}
297 297 </table>
298 298 </div>
299 299 % endif
300 300 </div>
301 301 </div>
302 302
303 303 <div id="pr-save" class="field" style="display: none;">
304 304 <div class="label-summary"></div>
305 305 <div class="input">
306 306 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
307 307 </div>
308 308 </div>
309 309 </div>
310 310 </div>
311 311 <div>
312 312 ## AUTHOR
313 313 <div class="reviewers-title block-right">
314 314 <div class="pr-details-title">
315 315 ${_('Author of this pull request')}
316 316 </div>
317 317 </div>
318 318 <div class="block-right pr-details-content reviewers">
319 319 <ul class="group_members">
320 320 <li>
321 321 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
322 322 </li>
323 323 </ul>
324 324 </div>
325 325
326 326 ## REVIEW RULES
327 327 <div id="review_rules" style="display: none" class="reviewers-title block-right">
328 328 <div class="pr-details-title">
329 329 ${_('Reviewer rules')}
330 330 %if c.allowed_to_update:
331 331 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
332 332 %endif
333 333 </div>
334 334 <div class="pr-reviewer-rules">
335 335 ## review rules will be appended here, by default reviewers logic
336 336 </div>
337 337 <input id="review_data" type="hidden" name="review_data" value="">
338 338 </div>
339 339
340 340 ## REVIEWERS
341 341 <div class="reviewers-title block-right">
342 342 <div class="pr-details-title">
343 343 ${_('Pull request reviewers')}
344 344 %if c.allowed_to_update:
345 345 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
346 346 %endif
347 347 </div>
348 348 </div>
349 349 <div id="reviewers" class="block-right pr-details-content reviewers">
350 350
351 351 ## members redering block
352 352 <input type="hidden" name="__start__" value="review_members:sequence">
353 353 <ul id="review_members" class="group_members">
354 354
355 355 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
356 356 <script>
357 357 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
358 358 var status = "${(status[0][1].status if status else 'not_reviewed')}";
359 359 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
360 360 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
361 361
362 362 var entry = renderTemplate('reviewMemberEntry', {
363 363 'member': member,
364 364 'mandatory': member.mandatory,
365 365 'reasons': member.reasons,
366 366 'allowed_to_update': allowed_to_update,
367 367 'review_status': status,
368 368 'review_status_label': status_lbl,
369 'user_group': member.user_group
369 'user_group': member.user_group,
370 'create': false
370 371 });
371 372 $('#review_members').append(entry)
372 373 </script>
373 374
374 375 % endfor
375 376
376 377 </ul>
377 378 <input type="hidden" name="__end__" value="review_members:sequence">
378 379 ## end members redering block
379 380
380 381 %if not c.pull_request.is_closed():
381 382 <div id="add_reviewer" class="ac" style="display: none;">
382 383 %if c.allowed_to_update:
383 384 % if not c.forbid_adding_reviewers:
384 385 <div id="add_reviewer_input" class="reviewer_ac">
385 386 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
386 387 <div id="reviewers_container"></div>
387 388 </div>
388 389 % endif
389 390 <div class="pull-right">
390 391 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
391 392 </div>
392 393 %endif
393 394 </div>
394 395 %endif
395 396 </div>
396 397 </div>
397 398 </div>
398 399 <div class="box">
399 400 ##DIFF
400 401 <div class="table" >
401 402 <div id="changeset_compare_view_content">
402 403 ##CS
403 404 % if c.missing_requirements:
404 405 <div class="box">
405 406 <div class="alert alert-warning">
406 407 <div>
407 408 <strong>${_('Missing requirements:')}</strong>
408 409 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
409 410 </div>
410 411 </div>
411 412 </div>
412 413 % elif c.missing_commits:
413 414 <div class="box">
414 415 <div class="alert alert-warning">
415 416 <div>
416 417 <strong>${_('Missing commits')}:</strong>
417 418 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
418 419 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
419 420 </div>
420 421 </div>
421 422 </div>
422 423 % endif
423 424
424 425 <div class="compare_view_commits_title">
425 426 % if not c.compare_mode:
426 427
427 428 % if c.at_version_pos:
428 429 <h4>
429 430 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
430 431 </h4>
431 432 % endif
432 433
433 434 <div class="pull-left">
434 435 <div class="btn-group">
435 436 <a
436 437 class="btn"
437 438 href="#"
438 439 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
439 440 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
440 441 </a>
441 442 <a
442 443 class="btn"
443 444 href="#"
444 445 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
445 446 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
446 447 </a>
447 448 </div>
448 449 </div>
449 450
450 451 <div class="pull-right">
451 452 % if c.allowed_to_update and not c.pull_request.is_closed():
452 453 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
453 454 % else:
454 455 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
455 456 % endif
456 457
457 458 </div>
458 459 % endif
459 460 </div>
460 461
461 462 % if not c.missing_commits:
462 463 % if c.compare_mode:
463 464 % if c.at_version:
464 465 <h4>
465 466 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
466 467 </h4>
467 468
468 469 <div class="subtitle-compare">
469 470 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
470 471 </div>
471 472
472 473 <div class="container">
473 474 <table class="rctable compare_view_commits">
474 475 <tr>
475 476 <th></th>
476 477 <th>${_('Time')}</th>
477 478 <th>${_('Author')}</th>
478 479 <th>${_('Commit')}</th>
479 480 <th></th>
480 481 <th>${_('Description')}</th>
481 482 </tr>
482 483
483 484 % for c_type, commit in c.commit_changes:
484 485 % if c_type in ['a', 'r']:
485 486 <%
486 487 if c_type == 'a':
487 488 cc_title = _('Commit added in displayed changes')
488 489 elif c_type == 'r':
489 490 cc_title = _('Commit removed in displayed changes')
490 491 else:
491 492 cc_title = ''
492 493 %>
493 494 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
494 495 <td>
495 496 <div class="commit-change-indicator color-${c_type}-border">
496 497 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
497 498 ${c_type.upper()}
498 499 </div>
499 500 </div>
500 501 </td>
501 502 <td class="td-time">
502 503 ${h.age_component(commit.date)}
503 504 </td>
504 505 <td class="td-user">
505 506 ${base.gravatar_with_user(commit.author, 16)}
506 507 </td>
507 508 <td class="td-hash">
508 509 <code>
509 510 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
510 511 r${commit.revision}:${h.short_id(commit.raw_id)}
511 512 </a>
512 513 ${h.hidden('revisions', commit.raw_id)}
513 514 </code>
514 515 </td>
515 516 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
516 517 <div class="show_more_col">
517 518 <i class="show_more"></i>
518 519 </div>
519 520 </td>
520 521 <td class="mid td-description">
521 522 <div class="log-container truncate-wrap">
522 523 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
523 524 ${h.urlify_commit_message(commit.message, c.repo_name)}
524 525 </div>
525 526 </div>
526 527 </td>
527 528 </tr>
528 529 % endif
529 530 % endfor
530 531 </table>
531 532 </div>
532 533
533 534 <script>
534 535 $('.expand_commit').on('click',function(e){
535 536 var target_expand = $(this);
536 537 var cid = target_expand.data('commitId');
537 538
538 539 if (target_expand.hasClass('open')){
539 540 $('#c-'+cid).css({
540 541 'height': '1.5em',
541 542 'white-space': 'nowrap',
542 543 'text-overflow': 'ellipsis',
543 544 'overflow':'hidden'
544 545 });
545 546 target_expand.removeClass('open');
546 547 }
547 548 else {
548 549 $('#c-'+cid).css({
549 550 'height': 'auto',
550 551 'white-space': 'pre-line',
551 552 'text-overflow': 'initial',
552 553 'overflow':'visible'
553 554 });
554 555 target_expand.addClass('open');
555 556 }
556 557 });
557 558 </script>
558 559
559 560 % endif
560 561
561 562 % else:
562 563 <%include file="/compare/compare_commits.mako" />
563 564 % endif
564 565
565 566 <div class="cs_files">
566 567 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
567 568 ${cbdiffs.render_diffset_menu()}
568 569 ${cbdiffs.render_diffset(
569 570 c.diffset, use_comments=True,
570 571 collapse_when_files_over=30,
571 572 disable_new_comments=not c.allowed_to_comment,
572 573 deleted_files_comments=c.deleted_files_comments)}
573 574 </div>
574 575 % else:
575 576 ## skipping commits we need to clear the view for missing commits
576 577 <div style="clear:both;"></div>
577 578 % endif
578 579
579 580 </div>
580 581 </div>
581 582
582 583 ## template for inline comment form
583 584 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
584 585
585 586 ## render general comments
586 587
587 588 <div id="comment-tr-show">
588 589 <div class="comment">
589 590 % if general_outdated_comm_count_ver:
590 591 <div class="meta">
591 592 % if general_outdated_comm_count_ver == 1:
592 593 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
593 594 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
594 595 % else:
595 596 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
596 597 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
597 598 % endif
598 599 </div>
599 600 % endif
600 601 </div>
601 602 </div>
602 603
603 604 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
604 605
605 606 % if not c.pull_request.is_closed():
606 607 ## merge status, and merge action
607 608 <div class="pull-request-merge">
608 609 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
609 610 </div>
610 611
611 612 ## main comment form and it status
612 613 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
613 614 pull_request_id=c.pull_request.pull_request_id),
614 615 c.pull_request_review_status,
615 616 is_pull_request=True, change_status=c.allowed_to_change_status)}
616 617 %endif
617 618
618 619 <script type="text/javascript">
619 620 if (location.hash) {
620 621 var result = splitDelimitedHash(location.hash);
621 622 var line = $('html').find(result.loc);
622 623 // show hidden comments if we use location.hash
623 624 if (line.hasClass('comment-general')) {
624 625 $(line).show();
625 626 } else if (line.hasClass('comment-inline')) {
626 627 $(line).show();
627 628 var $cb = $(line).closest('.cb');
628 629 $cb.removeClass('cb-collapsed')
629 630 }
630 631 if (line.length > 0){
631 632 offsetScroll(line, 70);
632 633 }
633 634 }
634 635
635 636 versionController = new VersionController();
636 637 versionController.init();
637 638
638 639 reviewersController = new ReviewersController();
639 640
640 641 $(function(){
641 642
642 643 // custom code mirror
643 644 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
644 645
645 646 var PRDetails = {
646 647 editButton: $('#open_edit_pullrequest'),
647 648 closeButton: $('#close_edit_pullrequest'),
648 649 deleteButton: $('#delete_pullrequest'),
649 650 viewFields: $('#pr-desc, #pr-title'),
650 651 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
651 652
652 653 init: function() {
653 654 var that = this;
654 655 this.editButton.on('click', function(e) { that.edit(); });
655 656 this.closeButton.on('click', function(e) { that.view(); });
656 657 },
657 658
658 659 edit: function(event) {
659 660 this.viewFields.hide();
660 661 this.editButton.hide();
661 662 this.deleteButton.hide();
662 663 this.closeButton.show();
663 664 this.editFields.show();
664 665 codeMirrorInstance.refresh();
665 666 },
666 667
667 668 view: function(event) {
668 669 this.editButton.show();
669 670 this.deleteButton.show();
670 671 this.editFields.hide();
671 672 this.closeButton.hide();
672 673 this.viewFields.show();
673 674 }
674 675 };
675 676
676 677 var ReviewersPanel = {
677 678 editButton: $('#open_edit_reviewers'),
678 679 closeButton: $('#close_edit_reviewers'),
679 680 addButton: $('#add_reviewer'),
680 681 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
681 682
682 683 init: function() {
683 684 var self = this;
684 685 this.editButton.on('click', function(e) { self.edit(); });
685 686 this.closeButton.on('click', function(e) { self.close(); });
686 687 },
687 688
688 689 edit: function(event) {
689 690 this.editButton.hide();
690 691 this.closeButton.show();
691 692 this.addButton.show();
692 693 this.removeButtons.css('visibility', 'visible');
693 694 // review rules
694 695 reviewersController.loadReviewRules(
695 696 ${c.pull_request.reviewer_data_json | n});
696 697 },
697 698
698 699 close: function(event) {
699 700 this.editButton.show();
700 701 this.closeButton.hide();
701 702 this.addButton.hide();
702 703 this.removeButtons.css('visibility', 'hidden');
703 704 // hide review rules
704 705 reviewersController.hideReviewRules()
705 706 }
706 707 };
707 708
708 709 PRDetails.init();
709 710 ReviewersPanel.init();
710 711
711 712 showOutdated = function(self){
712 713 $('.comment-inline.comment-outdated').show();
713 714 $('.filediff-outdated').show();
714 715 $('.showOutdatedComments').hide();
715 716 $('.hideOutdatedComments').show();
716 717 };
717 718
718 719 hideOutdated = function(self){
719 720 $('.comment-inline.comment-outdated').hide();
720 721 $('.filediff-outdated').hide();
721 722 $('.hideOutdatedComments').hide();
722 723 $('.showOutdatedComments').show();
723 724 };
724 725
725 726 refreshMergeChecks = function(){
726 727 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
727 728 $('.pull-request-merge').css('opacity', 0.3);
728 729 $('.action-buttons-extra').css('opacity', 0.3);
729 730
730 731 $('.pull-request-merge').load(
731 732 loadUrl, function() {
732 733 $('.pull-request-merge').css('opacity', 1);
733 734
734 735 $('.action-buttons-extra').css('opacity', 1);
735 736 injectCloseAction();
736 737 }
737 738 );
738 739 };
739 740
740 741 injectCloseAction = function() {
741 742 var closeAction = $('#close-pull-request-action').html();
742 743 var $actionButtons = $('.action-buttons-extra');
743 744 // clear the action before
744 745 $actionButtons.html("");
745 746 $actionButtons.html(closeAction);
746 747 };
747 748
748 749 closePullRequest = function (status) {
749 750 // inject closing flag
750 751 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
751 752 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
752 753 $(generalCommentForm.submitForm).submit();
753 754 };
754 755
755 756 $('#show-outdated-comments').on('click', function(e){
756 757 var button = $(this);
757 758 var outdated = $('.comment-outdated');
758 759
759 760 if (button.html() === "(Show)") {
760 761 button.html("(Hide)");
761 762 outdated.show();
762 763 } else {
763 764 button.html("(Show)");
764 765 outdated.hide();
765 766 }
766 767 });
767 768
768 769 $('.show-inline-comments').on('change', function(e){
769 770 var show = 'none';
770 771 var target = e.currentTarget;
771 772 if(target.checked){
772 773 show = ''
773 774 }
774 775 var boxid = $(target).attr('id_for');
775 776 var comments = $('#{0} .inline-comments'.format(boxid));
776 777 var fn_display = function(idx){
777 778 $(this).css('display', show);
778 779 };
779 780 $(comments).each(fn_display);
780 781 var btns = $('#{0} .inline-comments-button'.format(boxid));
781 782 $(btns).each(fn_display);
782 783 });
783 784
784 785 $('#merge_pull_request_form').submit(function() {
785 786 if (!$('#merge_pull_request').attr('disabled')) {
786 787 $('#merge_pull_request').attr('disabled', 'disabled');
787 788 }
788 789 return true;
789 790 });
790 791
791 792 $('#edit_pull_request').on('click', function(e){
792 793 var title = $('#pr-title-input').val();
793 794 var description = codeMirrorInstance.getValue();
794 795 editPullRequest(
795 796 "${c.repo_name}", "${c.pull_request.pull_request_id}",
796 797 title, description);
797 798 });
798 799
799 800 $('#update_pull_request').on('click', function(e){
800 801 $(this).attr('disabled', 'disabled');
801 802 $(this).addClass('disabled');
802 803 $(this).html(_gettext('Saving...'));
803 804 reviewersController.updateReviewers(
804 805 "${c.repo_name}", "${c.pull_request.pull_request_id}");
805 806 });
806 807
807 808 $('#update_commits').on('click', function(e){
808 809 var isDisabled = !$(e.currentTarget).attr('disabled');
809 810 $(e.currentTarget).attr('disabled', 'disabled');
810 811 $(e.currentTarget).addClass('disabled');
811 812 $(e.currentTarget).removeClass('btn-primary');
812 813 $(e.currentTarget).text(_gettext('Updating...'));
813 814 if(isDisabled){
814 815 updateCommits(
815 816 "${c.repo_name}", "${c.pull_request.pull_request_id}");
816 817 }
817 818 });
818 819 // fixing issue with caches on firefox
819 820 $('#update_commits').removeAttr("disabled");
820 821
821 822 $('.show-inline-comments').on('click', function(e){
822 823 var boxid = $(this).attr('data-comment-id');
823 824 var button = $(this);
824 825
825 826 if(button.hasClass("comments-visible")) {
826 827 $('#{0} .inline-comments'.format(boxid)).each(function(index){
827 828 $(this).hide();
828 829 });
829 830 button.removeClass("comments-visible");
830 831 } else {
831 832 $('#{0} .inline-comments'.format(boxid)).each(function(index){
832 833 $(this).show();
833 834 });
834 835 button.addClass("comments-visible");
835 836 }
836 837 });
837 838
838 839 // register submit callback on commentForm form to track TODOs
839 840 window.commentFormGlobalSubmitSuccessCallback = function(){
840 841 refreshMergeChecks();
841 842 };
842 843 // initial injection
843 844 injectCloseAction();
844 845
845 846 ReviewerAutoComplete('#user');
846 847
847 848 })
848 849 </script>
849 850
850 851 </div>
851 852 </div>
852 853
853 854 </%def>
General Comments 0
You need to be logged in to leave comments. Login now