##// END OF EJS Templates
pull-requests: fixed cases with default expected refs are closed or unavailable....
ergo -
r2555:1d84c2ba stable
parent child Browse files
Show More
@@ -1,587 +1,593 b''
1 // # Copyright (C) 2010-2018 RhodeCode GmbH
1 // # Copyright (C) 2010-2018 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 $('#save').attr('disabled', 'disabled');
44 $('#save').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#save').removeAttr('disabled');
47 $('#save').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(sourceRef, elements, limit) {
73 var getTitleAndDescription = function(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).find('td.td-description .message').data('messageRaw');
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
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 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 }
84 }
85 else {
85 else {
86 // use reference name
86 // use reference name
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 }
88 }
89
89
90 return [title, desc]
90 return [title, desc]
91 };
91 };
92
92
93
93
94
94
95 ReviewersController = function () {
95 ReviewersController = function () {
96 var self = this;
96 var self = this;
97 this.$reviewRulesContainer = $('#review_rules');
97 this.$reviewRulesContainer = $('#review_rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 this.forbidReviewUsers = undefined;
99 this.forbidReviewUsers = undefined;
100 this.$reviewMembers = $('#review_members');
100 this.$reviewMembers = $('#review_members');
101 this.currentRequest = null;
101 this.currentRequest = null;
102
102
103 this.defaultForbidReviewUsers = function() {
103 this.defaultForbidReviewUsers = function() {
104 return [
104 return [
105 {'username': 'default',
105 {'username': 'default',
106 'user_id': templateContext.default_user.user_id}
106 'user_id': templateContext.default_user.user_id}
107 ];
107 ];
108 };
108 };
109
109
110 this.hideReviewRules = function() {
110 this.hideReviewRules = function() {
111 self.$reviewRulesContainer.hide();
111 self.$reviewRulesContainer.hide();
112 };
112 };
113
113
114 this.showReviewRules = function() {
114 this.showReviewRules = function() {
115 self.$reviewRulesContainer.show();
115 self.$reviewRulesContainer.show();
116 };
116 };
117
117
118 this.addRule = function(ruleText) {
118 this.addRule = function(ruleText) {
119 self.showReviewRules();
119 self.showReviewRules();
120 return '<div>- {0}</div>'.format(ruleText)
120 return '<div>- {0}</div>'.format(ruleText)
121 };
121 };
122
122
123 this.loadReviewRules = function(data) {
123 this.loadReviewRules = function(data) {
124 // reset forbidden Users
124 // reset forbidden Users
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126
126
127 // reset state of review rules
127 // reset state of review rules
128 self.$rulesList.html('');
128 self.$rulesList.html('');
129
129
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 // default rule, case for older repo that don't have any rules stored
131 // default rule, case for older repo that don't have any rules stored
132 self.$rulesList.append(
132 self.$rulesList.append(
133 self.addRule(
133 self.addRule(
134 _gettext('All reviewers must vote.'))
134 _gettext('All reviewers must vote.'))
135 );
135 );
136 return self.forbidReviewUsers
136 return self.forbidReviewUsers
137 }
137 }
138
138
139 if (data.rules.voting !== undefined) {
139 if (data.rules.voting !== undefined) {
140 if (data.rules.voting < 0) {
140 if (data.rules.voting < 0) {
141 self.$rulesList.append(
141 self.$rulesList.append(
142 self.addRule(
142 self.addRule(
143 _gettext('All individual reviewers must vote.'))
143 _gettext('All individual reviewers must vote.'))
144 )
144 )
145 } else if (data.rules.voting === 1) {
145 } else if (data.rules.voting === 1) {
146 self.$rulesList.append(
146 self.$rulesList.append(
147 self.addRule(
147 self.addRule(
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 )
149 )
150
150
151 } else {
151 } else {
152 self.$rulesList.append(
152 self.$rulesList.append(
153 self.addRule(
153 self.addRule(
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 )
155 )
156 }
156 }
157 }
157 }
158
158
159 if (data.rules.voting_groups !== undefined) {
159 if (data.rules.voting_groups !== undefined) {
160 $.each(data.rules.voting_groups, function(index, rule_data) {
160 $.each(data.rules.voting_groups, function(index, rule_data) {
161 self.$rulesList.append(
161 self.$rulesList.append(
162 self.addRule(rule_data.text)
162 self.addRule(rule_data.text)
163 )
163 )
164 });
164 });
165 }
165 }
166
166
167 if (data.rules.use_code_authors_for_review) {
167 if (data.rules.use_code_authors_for_review) {
168 self.$rulesList.append(
168 self.$rulesList.append(
169 self.addRule(
169 self.addRule(
170 _gettext('Reviewers picked from source code changes.'))
170 _gettext('Reviewers picked from source code changes.'))
171 )
171 )
172 }
172 }
173 if (data.rules.forbid_adding_reviewers) {
173 if (data.rules.forbid_adding_reviewers) {
174 $('#add_reviewer_input').remove();
174 $('#add_reviewer_input').remove();
175 self.$rulesList.append(
175 self.$rulesList.append(
176 self.addRule(
176 self.addRule(
177 _gettext('Adding new reviewers is forbidden.'))
177 _gettext('Adding new reviewers is forbidden.'))
178 )
178 )
179 }
179 }
180 if (data.rules.forbid_author_to_review) {
180 if (data.rules.forbid_author_to_review) {
181 self.forbidReviewUsers.push(data.rules_data.pr_author);
181 self.forbidReviewUsers.push(data.rules_data.pr_author);
182 self.$rulesList.append(
182 self.$rulesList.append(
183 self.addRule(
183 self.addRule(
184 _gettext('Author is not allowed to be a reviewer.'))
184 _gettext('Author is not allowed to be a reviewer.'))
185 )
185 )
186 }
186 }
187 if (data.rules.forbid_commit_author_to_review) {
187 if (data.rules.forbid_commit_author_to_review) {
188
188
189 if (data.rules_data.forbidden_users) {
189 if (data.rules_data.forbidden_users) {
190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
191 self.forbidReviewUsers.push(member_data)
191 self.forbidReviewUsers.push(member_data)
192 });
192 });
193
193
194 }
194 }
195
195
196 self.$rulesList.append(
196 self.$rulesList.append(
197 self.addRule(
197 self.addRule(
198 _gettext('Commit Authors are not allowed to be a reviewer.'))
198 _gettext('Commit Authors are not allowed to be a reviewer.'))
199 )
199 )
200 }
200 }
201
201
202 return self.forbidReviewUsers
202 return self.forbidReviewUsers
203 };
203 };
204
204
205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
206
206
207 if (self.currentRequest) {
207 if (self.currentRequest) {
208 // make sure we cleanup old running requests before triggering this
208 // make sure we cleanup old running requests before triggering this
209 // again
209 // again
210 self.currentRequest.abort();
210 self.currentRequest.abort();
211 }
211 }
212
212
213 $('.calculate-reviewers').show();
213 $('.calculate-reviewers').show();
214 // reset reviewer members
214 // reset reviewer members
215 self.$reviewMembers.empty();
215 self.$reviewMembers.empty();
216
216
217 prButtonLock(true, null, 'reviewers');
217 prButtonLock(true, null, 'reviewers');
218 $('#user').hide(); // hide user autocomplete before load
218 $('#user').hide(); // hide user autocomplete before load
219
219
220 if (sourceRef.length !== 3 || targetRef.length !== 3) {
221 // don't load defaults in case we're missing some refs...
222 $('.calculate-reviewers').hide();
223 return
224 }
225
220 var url = pyroutes.url('repo_default_reviewers_data',
226 var url = pyroutes.url('repo_default_reviewers_data',
221 {
227 {
222 'repo_name': templateContext.repo_name,
228 'repo_name': templateContext.repo_name,
223 'source_repo': sourceRepo,
229 'source_repo': sourceRepo,
224 'source_ref': sourceRef[2],
230 'source_ref': sourceRef[2],
225 'target_repo': targetRepo,
231 'target_repo': targetRepo,
226 'target_ref': targetRef[2]
232 'target_ref': targetRef[2]
227 });
233 });
228
234
229 self.currentRequest = $.get(url)
235 self.currentRequest = $.get(url)
230 .done(function(data) {
236 .done(function(data) {
231 self.currentRequest = null;
237 self.currentRequest = null;
232
238
233 // review rules
239 // review rules
234 self.loadReviewRules(data);
240 self.loadReviewRules(data);
235
241
236 for (var i = 0; i < data.reviewers.length; i++) {
242 for (var i = 0; i < data.reviewers.length; i++) {
237 var reviewer = data.reviewers[i];
243 var reviewer = data.reviewers[i];
238 self.addReviewMember(
244 self.addReviewMember(
239 reviewer, reviewer.reasons, reviewer.mandatory);
245 reviewer, reviewer.reasons, reviewer.mandatory);
240 }
246 }
241 $('.calculate-reviewers').hide();
247 $('.calculate-reviewers').hide();
242 prButtonLock(false, null, 'reviewers');
248 prButtonLock(false, null, 'reviewers');
243 $('#user').show(); // show user autocomplete after load
249 $('#user').show(); // show user autocomplete after load
244 });
250 });
245 };
251 };
246
252
247 // check those, refactor
253 // check those, refactor
248 this.removeReviewMember = function(reviewer_id, mark_delete) {
254 this.removeReviewMember = function(reviewer_id, mark_delete) {
249 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
250
256
251 if(typeof(mark_delete) === undefined){
257 if(typeof(mark_delete) === undefined){
252 mark_delete = false;
258 mark_delete = false;
253 }
259 }
254
260
255 if(mark_delete === true){
261 if(mark_delete === true){
256 if (reviewer){
262 if (reviewer){
257 // now delete the input
263 // now delete the input
258 $('#reviewer_{0} input'.format(reviewer_id)).remove();
264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
259 // mark as to-delete
265 // mark as to-delete
260 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
261 obj.addClass('to-delete');
267 obj.addClass('to-delete');
262 obj.css({"text-decoration":"line-through", "opacity": 0.5});
268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
263 }
269 }
264 }
270 }
265 else{
271 else{
266 $('#reviewer_{0}'.format(reviewer_id)).remove();
272 $('#reviewer_{0}'.format(reviewer_id)).remove();
267 }
273 }
268 };
274 };
269 this.reviewMemberEntry = function() {
275 this.reviewMemberEntry = function() {
270
276
271 };
277 };
272 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
273 var members = self.$reviewMembers.get(0);
279 var members = self.$reviewMembers.get(0);
274 var id = reviewer_obj.user_id;
280 var id = reviewer_obj.user_id;
275 var username = reviewer_obj.username;
281 var username = reviewer_obj.username;
276
282
277 var reasons = reasons || [];
283 var reasons = reasons || [];
278 var mandatory = mandatory || false;
284 var mandatory = mandatory || false;
279
285
280 // register IDS to check if we don't have this ID already in
286 // register IDS to check if we don't have this ID already in
281 var currentIds = [];
287 var currentIds = [];
282 var _els = self.$reviewMembers.find('li').toArray();
288 var _els = self.$reviewMembers.find('li').toArray();
283 for (el in _els){
289 for (el in _els){
284 currentIds.push(_els[el].id)
290 currentIds.push(_els[el].id)
285 }
291 }
286
292
287 var userAllowedReview = function(userId) {
293 var userAllowedReview = function(userId) {
288 var allowed = true;
294 var allowed = true;
289 $.each(self.forbidReviewUsers, function(index, member_data) {
295 $.each(self.forbidReviewUsers, function(index, member_data) {
290 if (parseInt(userId) === member_data['user_id']) {
296 if (parseInt(userId) === member_data['user_id']) {
291 allowed = false;
297 allowed = false;
292 return false // breaks the loop
298 return false // breaks the loop
293 }
299 }
294 });
300 });
295 return allowed
301 return allowed
296 };
302 };
297
303
298 var userAllowed = userAllowedReview(id);
304 var userAllowed = userAllowedReview(id);
299 if (!userAllowed){
305 if (!userAllowed){
300 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
306 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
301 } else {
307 } else {
302 // only add if it's not there
308 // only add if it's not there
303 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
304
310
305 if (alreadyReviewer) {
311 if (alreadyReviewer) {
306 alert(_gettext('User `{0}` already in reviewers').format(username));
312 alert(_gettext('User `{0}` already in reviewers').format(username));
307 } else {
313 } else {
308 members.innerHTML += renderTemplate('reviewMemberEntry', {
314 members.innerHTML += renderTemplate('reviewMemberEntry', {
309 'member': reviewer_obj,
315 'member': reviewer_obj,
310 'mandatory': mandatory,
316 'mandatory': mandatory,
311 'allowed_to_update': true,
317 'allowed_to_update': true,
312 'review_status': 'not_reviewed',
318 'review_status': 'not_reviewed',
313 'review_status_label': _gettext('Not Reviewed'),
319 'review_status_label': _gettext('Not Reviewed'),
314 'reasons': reasons,
320 'reasons': reasons,
315 'create': true
321 'create': true
316 });
322 });
317 }
323 }
318 }
324 }
319
325
320 };
326 };
321
327
322 this.updateReviewers = function(repo_name, pull_request_id){
328 this.updateReviewers = function(repo_name, pull_request_id){
323 var postData = $('#reviewers input').serialize();
329 var postData = $('#reviewers input').serialize();
324 _updatePullRequest(repo_name, pull_request_id, postData);
330 _updatePullRequest(repo_name, pull_request_id, postData);
325 };
331 };
326
332
327 };
333 };
328
334
329
335
330 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
336 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
331 var url = pyroutes.url(
337 var url = pyroutes.url(
332 'pullrequest_update',
338 'pullrequest_update',
333 {"repo_name": repo_name, "pull_request_id": pull_request_id});
339 {"repo_name": repo_name, "pull_request_id": pull_request_id});
334 if (typeof postData === 'string' ) {
340 if (typeof postData === 'string' ) {
335 postData += '&csrf_token=' + CSRF_TOKEN;
341 postData += '&csrf_token=' + CSRF_TOKEN;
336 } else {
342 } else {
337 postData.csrf_token = CSRF_TOKEN;
343 postData.csrf_token = CSRF_TOKEN;
338 }
344 }
339 var success = function(o) {
345 var success = function(o) {
340 window.location.reload();
346 window.location.reload();
341 };
347 };
342 ajaxPOST(url, postData, success);
348 ajaxPOST(url, postData, success);
343 };
349 };
344
350
345 /**
351 /**
346 * PULL REQUEST update commits
352 * PULL REQUEST update commits
347 */
353 */
348 var updateCommits = function(repo_name, pull_request_id) {
354 var updateCommits = function(repo_name, pull_request_id) {
349 var postData = {
355 var postData = {
350 'update_commits': true};
356 'update_commits': true};
351 _updatePullRequest(repo_name, pull_request_id, postData);
357 _updatePullRequest(repo_name, pull_request_id, postData);
352 };
358 };
353
359
354
360
355 /**
361 /**
356 * PULL REQUEST edit info
362 * PULL REQUEST edit info
357 */
363 */
358 var editPullRequest = function(repo_name, pull_request_id, title, description) {
364 var editPullRequest = function(repo_name, pull_request_id, title, description) {
359 var url = pyroutes.url(
365 var url = pyroutes.url(
360 'pullrequest_update',
366 'pullrequest_update',
361 {"repo_name": repo_name, "pull_request_id": pull_request_id});
367 {"repo_name": repo_name, "pull_request_id": pull_request_id});
362
368
363 var postData = {
369 var postData = {
364 'title': title,
370 'title': title,
365 'description': description,
371 'description': description,
366 'edit_pull_request': true,
372 'edit_pull_request': true,
367 'csrf_token': CSRF_TOKEN
373 'csrf_token': CSRF_TOKEN
368 };
374 };
369 var success = function(o) {
375 var success = function(o) {
370 window.location.reload();
376 window.location.reload();
371 };
377 };
372 ajaxPOST(url, postData, success);
378 ajaxPOST(url, postData, success);
373 };
379 };
374
380
375 var initPullRequestsCodeMirror = function (textAreaId) {
381 var initPullRequestsCodeMirror = function (textAreaId) {
376 var ta = $(textAreaId).get(0);
382 var ta = $(textAreaId).get(0);
377 var initialHeight = '100px';
383 var initialHeight = '100px';
378
384
379 // default options
385 // default options
380 var codeMirrorOptions = {
386 var codeMirrorOptions = {
381 mode: "text",
387 mode: "text",
382 lineNumbers: false,
388 lineNumbers: false,
383 indentUnit: 4,
389 indentUnit: 4,
384 theme: 'rc-input'
390 theme: 'rc-input'
385 };
391 };
386
392
387 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
393 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
388 // marker for manually set description
394 // marker for manually set description
389 codeMirrorInstance._userDefinedDesc = false;
395 codeMirrorInstance._userDefinedDesc = false;
390 codeMirrorInstance.setSize(null, initialHeight);
396 codeMirrorInstance.setSize(null, initialHeight);
391 codeMirrorInstance.on("change", function(instance, changeObj) {
397 codeMirrorInstance.on("change", function(instance, changeObj) {
392 var height = initialHeight;
398 var height = initialHeight;
393 var lines = instance.lineCount();
399 var lines = instance.lineCount();
394 if (lines > 6 && lines < 20) {
400 if (lines > 6 && lines < 20) {
395 height = "auto"
401 height = "auto"
396 }
402 }
397 else if (lines >= 20) {
403 else if (lines >= 20) {
398 height = 20 * 15;
404 height = 20 * 15;
399 }
405 }
400 instance.setSize(null, height);
406 instance.setSize(null, height);
401
407
402 // detect if the change was trigger by auto desc, or user input
408 // detect if the change was trigger by auto desc, or user input
403 changeOrigin = changeObj.origin;
409 changeOrigin = changeObj.origin;
404
410
405 if (changeOrigin === "setValue") {
411 if (changeOrigin === "setValue") {
406 cmLog.debug('Change triggered by setValue');
412 cmLog.debug('Change triggered by setValue');
407 }
413 }
408 else {
414 else {
409 cmLog.debug('user triggered change !');
415 cmLog.debug('user triggered change !');
410 // set special marker to indicate user has created an input.
416 // set special marker to indicate user has created an input.
411 instance._userDefinedDesc = true;
417 instance._userDefinedDesc = true;
412 }
418 }
413
419
414 });
420 });
415
421
416 return codeMirrorInstance
422 return codeMirrorInstance
417 };
423 };
418
424
419 /**
425 /**
420 * Reviewer autocomplete
426 * Reviewer autocomplete
421 */
427 */
422 var ReviewerAutoComplete = function(inputId) {
428 var ReviewerAutoComplete = function(inputId) {
423 $(inputId).autocomplete({
429 $(inputId).autocomplete({
424 serviceUrl: pyroutes.url('user_autocomplete_data'),
430 serviceUrl: pyroutes.url('user_autocomplete_data'),
425 minChars:2,
431 minChars:2,
426 maxHeight:400,
432 maxHeight:400,
427 deferRequestBy: 300, //miliseconds
433 deferRequestBy: 300, //miliseconds
428 showNoSuggestionNotice: true,
434 showNoSuggestionNotice: true,
429 tabDisabled: true,
435 tabDisabled: true,
430 autoSelectFirst: true,
436 autoSelectFirst: true,
431 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
437 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
432 formatResult: autocompleteFormatResult,
438 formatResult: autocompleteFormatResult,
433 lookupFilter: autocompleteFilterResult,
439 lookupFilter: autocompleteFilterResult,
434 onSelect: function(element, data) {
440 onSelect: function(element, data) {
435 var mandatory = false;
441 var mandatory = false;
436 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
442 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
437
443
438 // add whole user groups
444 // add whole user groups
439 if (data.value_type == 'user_group') {
445 if (data.value_type == 'user_group') {
440 reasons.push(_gettext('member of "{0}"').format(data.value_display));
446 reasons.push(_gettext('member of "{0}"').format(data.value_display));
441
447
442 $.each(data.members, function(index, member_data) {
448 $.each(data.members, function(index, member_data) {
443 var reviewer = member_data;
449 var reviewer = member_data;
444 reviewer['user_id'] = member_data['id'];
450 reviewer['user_id'] = member_data['id'];
445 reviewer['gravatar_link'] = member_data['icon_link'];
451 reviewer['gravatar_link'] = member_data['icon_link'];
446 reviewer['user_link'] = member_data['profile_link'];
452 reviewer['user_link'] = member_data['profile_link'];
447 reviewer['rules'] = [];
453 reviewer['rules'] = [];
448 reviewersController.addReviewMember(reviewer, reasons, mandatory);
454 reviewersController.addReviewMember(reviewer, reasons, mandatory);
449 })
455 })
450 }
456 }
451 // add single user
457 // add single user
452 else {
458 else {
453 var reviewer = data;
459 var reviewer = data;
454 reviewer['user_id'] = data['id'];
460 reviewer['user_id'] = data['id'];
455 reviewer['gravatar_link'] = data['icon_link'];
461 reviewer['gravatar_link'] = data['icon_link'];
456 reviewer['user_link'] = data['profile_link'];
462 reviewer['user_link'] = data['profile_link'];
457 reviewer['rules'] = [];
463 reviewer['rules'] = [];
458 reviewersController.addReviewMember(reviewer, reasons, mandatory);
464 reviewersController.addReviewMember(reviewer, reasons, mandatory);
459 }
465 }
460
466
461 $(inputId).val('');
467 $(inputId).val('');
462 }
468 }
463 });
469 });
464 };
470 };
465
471
466
472
467 VersionController = function () {
473 VersionController = function () {
468 var self = this;
474 var self = this;
469 this.$verSource = $('input[name=ver_source]');
475 this.$verSource = $('input[name=ver_source]');
470 this.$verTarget = $('input[name=ver_target]');
476 this.$verTarget = $('input[name=ver_target]');
471 this.$showVersionDiff = $('#show-version-diff');
477 this.$showVersionDiff = $('#show-version-diff');
472
478
473 this.adjustRadioSelectors = function (curNode) {
479 this.adjustRadioSelectors = function (curNode) {
474 var getVal = function (item) {
480 var getVal = function (item) {
475 if (item == 'latest') {
481 if (item == 'latest') {
476 return Number.MAX_SAFE_INTEGER
482 return Number.MAX_SAFE_INTEGER
477 }
483 }
478 else {
484 else {
479 return parseInt(item)
485 return parseInt(item)
480 }
486 }
481 };
487 };
482
488
483 var curVal = getVal($(curNode).val());
489 var curVal = getVal($(curNode).val());
484 var cleared = false;
490 var cleared = false;
485
491
486 $.each(self.$verSource, function (index, value) {
492 $.each(self.$verSource, function (index, value) {
487 var elVal = getVal($(value).val());
493 var elVal = getVal($(value).val());
488
494
489 if (elVal > curVal) {
495 if (elVal > curVal) {
490 if ($(value).is(':checked')) {
496 if ($(value).is(':checked')) {
491 cleared = true;
497 cleared = true;
492 }
498 }
493 $(value).attr('disabled', 'disabled');
499 $(value).attr('disabled', 'disabled');
494 $(value).removeAttr('checked');
500 $(value).removeAttr('checked');
495 $(value).css({'opacity': 0.1});
501 $(value).css({'opacity': 0.1});
496 }
502 }
497 else {
503 else {
498 $(value).css({'opacity': 1});
504 $(value).css({'opacity': 1});
499 $(value).removeAttr('disabled');
505 $(value).removeAttr('disabled');
500 }
506 }
501 });
507 });
502
508
503 if (cleared) {
509 if (cleared) {
504 // if we unchecked an active, set the next one to same loc.
510 // if we unchecked an active, set the next one to same loc.
505 $(this.$verSource).filter('[value={0}]'.format(
511 $(this.$verSource).filter('[value={0}]'.format(
506 curVal)).attr('checked', 'checked');
512 curVal)).attr('checked', 'checked');
507 }
513 }
508
514
509 self.setLockAction(false,
515 self.setLockAction(false,
510 $(curNode).data('verPos'),
516 $(curNode).data('verPos'),
511 $(this.$verSource).filter(':checked').data('verPos')
517 $(this.$verSource).filter(':checked').data('verPos')
512 );
518 );
513 };
519 };
514
520
515
521
516 this.attachVersionListener = function () {
522 this.attachVersionListener = function () {
517 self.$verTarget.change(function (e) {
523 self.$verTarget.change(function (e) {
518 self.adjustRadioSelectors(this)
524 self.adjustRadioSelectors(this)
519 });
525 });
520 self.$verSource.change(function (e) {
526 self.$verSource.change(function (e) {
521 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
527 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
522 });
528 });
523 };
529 };
524
530
525 this.init = function () {
531 this.init = function () {
526
532
527 var curNode = self.$verTarget.filter(':checked');
533 var curNode = self.$verTarget.filter(':checked');
528 self.adjustRadioSelectors(curNode);
534 self.adjustRadioSelectors(curNode);
529 self.setLockAction(true);
535 self.setLockAction(true);
530 self.attachVersionListener();
536 self.attachVersionListener();
531
537
532 };
538 };
533
539
534 this.setLockAction = function (state, selectedVersion, otherVersion) {
540 this.setLockAction = function (state, selectedVersion, otherVersion) {
535 var $showVersionDiff = this.$showVersionDiff;
541 var $showVersionDiff = this.$showVersionDiff;
536
542
537 if (state) {
543 if (state) {
538 $showVersionDiff.attr('disabled', 'disabled');
544 $showVersionDiff.attr('disabled', 'disabled');
539 $showVersionDiff.addClass('disabled');
545 $showVersionDiff.addClass('disabled');
540 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
546 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
541 }
547 }
542 else {
548 else {
543 $showVersionDiff.removeAttr('disabled');
549 $showVersionDiff.removeAttr('disabled');
544 $showVersionDiff.removeClass('disabled');
550 $showVersionDiff.removeClass('disabled');
545
551
546 if (selectedVersion == otherVersion) {
552 if (selectedVersion == otherVersion) {
547 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
553 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
548 } else {
554 } else {
549 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
555 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
550 }
556 }
551 }
557 }
552
558
553 };
559 };
554
560
555 this.showVersionDiff = function () {
561 this.showVersionDiff = function () {
556 var target = self.$verTarget.filter(':checked');
562 var target = self.$verTarget.filter(':checked');
557 var source = self.$verSource.filter(':checked');
563 var source = self.$verSource.filter(':checked');
558
564
559 if (target.val() && source.val()) {
565 if (target.val() && source.val()) {
560 var params = {
566 var params = {
561 'pull_request_id': templateContext.pull_request_data.pull_request_id,
567 'pull_request_id': templateContext.pull_request_data.pull_request_id,
562 'repo_name': templateContext.repo_name,
568 'repo_name': templateContext.repo_name,
563 'version': target.val(),
569 'version': target.val(),
564 'from_version': source.val()
570 'from_version': source.val()
565 };
571 };
566 window.location = pyroutes.url('pullrequest_show', params)
572 window.location = pyroutes.url('pullrequest_show', params)
567 }
573 }
568
574
569 return false;
575 return false;
570 };
576 };
571
577
572 this.toggleVersionView = function (elem) {
578 this.toggleVersionView = function (elem) {
573
579
574 if (this.$showVersionDiff.is(':visible')) {
580 if (this.$showVersionDiff.is(':visible')) {
575 $('.version-pr').hide();
581 $('.version-pr').hide();
576 this.$showVersionDiff.hide();
582 this.$showVersionDiff.hide();
577 $(elem).html($(elem).data('toggleOn'))
583 $(elem).html($(elem).data('toggleOn'))
578 } else {
584 } else {
579 $('.version-pr').show();
585 $('.version-pr').show();
580 this.$showVersionDiff.show();
586 this.$showVersionDiff.show();
581 $(elem).html($(elem).data('toggleOff'))
587 $(elem).html($(elem).data('toggleOff'))
582 }
588 }
583
589
584 return false
590 return false
585 }
591 }
586
592
587 }; No newline at end of file
593 };
@@ -1,530 +1,537 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('New pull request')}
8 ${_('New pull request')}
9 </%def>
9 </%def>
10
10
11 <%def name="menu_bar_nav()">
11 <%def name="menu_bar_nav()">
12 ${self.menu_items(active='repositories')}
12 ${self.menu_items(active='repositories')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_subnav()">
15 <%def name="menu_bar_subnav()">
16 ${self.repo_menu(active='showpullrequest')}
16 ${self.repo_menu(active='showpullrequest')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.repo_page_title(c.rhodecode_db_repo)}
22 ${self.repo_page_title(c.rhodecode_db_repo)}
23 </div>
23 </div>
24
24
25 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
25 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
26
26
27 ${self.breadcrumbs()}
27 ${self.breadcrumbs()}
28
28
29 <div class="box pr-summary">
29 <div class="box pr-summary">
30
30
31 <div class="summary-details block-left">
31 <div class="summary-details block-left">
32
32
33
33
34 <div class="pr-details-title">
34 <div class="pr-details-title">
35 ${_('Pull request summary')}
35 ${_('Pull request summary')}
36 </div>
36 </div>
37
37
38 <div class="form" style="padding-top: 10px">
38 <div class="form" style="padding-top: 10px">
39 <!-- fields -->
39 <!-- fields -->
40
40
41 <div class="fields" >
41 <div class="fields" >
42
42
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="pullrequest_title">${_('Title')}:</label>
45 <label for="pullrequest_title">${_('Title')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
48 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label label-textarea">
53 <div class="label label-textarea">
54 <label for="pullrequest_desc">${_('Description')}:</label>
54 <label for="pullrequest_desc">${_('Description')}:</label>
55 </div>
55 </div>
56 <div class="textarea text-area editor">
56 <div class="textarea text-area editor">
57 ${h.textarea('pullrequest_desc',size=30, )}
57 ${h.textarea('pullrequest_desc',size=30, )}
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <div class="field">
62 <div class="field">
63 <div class="label label-textarea">
63 <div class="label label-textarea">
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
65 </div>
65 </div>
66
66
67 ## TODO: johbo: Abusing the "content" class here to get the
67 ## TODO: johbo: Abusing the "content" class here to get the
68 ## desired effect. Should be replaced by a proper solution.
68 ## desired effect. Should be replaced by a proper solution.
69
69
70 ##ORG
70 ##ORG
71 <div class="content">
71 <div class="content">
72 <strong>${_('Source repository')}:</strong>
72 <strong>${_('Source repository')}:</strong>
73 ${c.rhodecode_db_repo.description}
73 ${c.rhodecode_db_repo.description}
74 </div>
74 </div>
75 <div class="content">
75 <div class="content">
76 ${h.hidden('source_repo')}
76 ${h.hidden('source_repo')}
77 ${h.hidden('source_ref')}
77 ${h.hidden('source_ref')}
78 </div>
78 </div>
79
79
80 ##OTHER, most Probably the PARENT OF THIS FORK
80 ##OTHER, most Probably the PARENT OF THIS FORK
81 <div class="content">
81 <div class="content">
82 ## filled with JS
82 ## filled with JS
83 <div id="target_repo_desc"></div>
83 <div id="target_repo_desc"></div>
84 </div>
84 </div>
85
85
86 <div class="content">
86 <div class="content">
87 ${h.hidden('target_repo')}
87 ${h.hidden('target_repo')}
88 ${h.hidden('target_ref')}
88 ${h.hidden('target_ref')}
89 <span id="target_ref_loading" style="display: none">
89 <span id="target_ref_loading" style="display: none">
90 ${_('Loading refs...')}
90 ${_('Loading refs...')}
91 </span>
91 </span>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label label-textarea">
96 <div class="label label-textarea">
97 <label for="pullrequest_submit"></label>
97 <label for="pullrequest_submit"></label>
98 </div>
98 </div>
99 <div class="input">
99 <div class="input">
100 <div class="pr-submit-button">
100 <div class="pr-submit-button">
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
101 ${h.submit('save',_('Submit Pull Request'),class_="btn")}
102 </div>
102 </div>
103 <div id="pr_open_message"></div>
103 <div id="pr_open_message"></div>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="pr-spacing-container"></div>
107 <div class="pr-spacing-container"></div>
108 </div>
108 </div>
109 </div>
109 </div>
110 </div>
110 </div>
111 <div>
111 <div>
112 ## AUTHOR
112 ## AUTHOR
113 <div class="reviewers-title block-right">
113 <div class="reviewers-title block-right">
114 <div class="pr-details-title">
114 <div class="pr-details-title">
115 ${_('Author of this pull request')}
115 ${_('Author of this pull request')}
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="block-right pr-details-content reviewers">
118 <div class="block-right pr-details-content reviewers">
119 <ul class="group_members">
119 <ul class="group_members">
120 <li>
120 <li>
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 </li>
122 </li>
123 </ul>
123 </ul>
124 </div>
124 </div>
125
125
126 ## REVIEW RULES
126 ## REVIEW RULES
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div class="pr-details-title">
128 <div class="pr-details-title">
129 ${_('Reviewer rules')}
129 ${_('Reviewer rules')}
130 </div>
130 </div>
131 <div class="pr-reviewer-rules">
131 <div class="pr-reviewer-rules">
132 ## review rules will be appended here, by default reviewers logic
132 ## review rules will be appended here, by default reviewers logic
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 ## REVIEWERS
136 ## REVIEWERS
137 <div class="reviewers-title block-right">
137 <div class="reviewers-title block-right">
138 <div class="pr-details-title">
138 <div class="pr-details-title">
139 ${_('Pull request reviewers')}
139 ${_('Pull request reviewers')}
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 </div>
141 </div>
142 </div>
142 </div>
143 <div id="reviewers" class="block-right pr-details-content reviewers">
143 <div id="reviewers" class="block-right pr-details-content reviewers">
144 ## members goes here, filled via JS based on initial selection !
144 ## members goes here, filled via JS based on initial selection !
145 <input type="hidden" name="__start__" value="review_members:sequence">
145 <input type="hidden" name="__start__" value="review_members:sequence">
146 <ul id="review_members" class="group_members"></ul>
146 <ul id="review_members" class="group_members"></ul>
147 <input type="hidden" name="__end__" value="review_members:sequence">
147 <input type="hidden" name="__end__" value="review_members:sequence">
148 <div id="add_reviewer_input" class='ac'>
148 <div id="add_reviewer_input" class='ac'>
149 <div class="reviewer_ac">
149 <div class="reviewer_ac">
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 <div id="reviewers_container"></div>
151 <div id="reviewers_container"></div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div class="box">
157 <div class="box">
158 <div>
158 <div>
159 ## overview pulled by ajax
159 ## overview pulled by ajax
160 <div id="pull_request_overview"></div>
160 <div id="pull_request_overview"></div>
161 </div>
161 </div>
162 </div>
162 </div>
163 ${h.end_form()}
163 ${h.end_form()}
164 </div>
164 </div>
165
165
166 <script type="text/javascript">
166 <script type="text/javascript">
167 $(function(){
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172
172
173 var $pullRequestForm = $('#pull_request_form');
173 var $pullRequestForm = $('#pull_request_form');
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
178
178
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
181
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
184
185 var calculateContainerWidth = function() {
185 var calculateContainerWidth = function() {
186 var maxWidth = 0;
186 var maxWidth = 0;
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 $.each(repoSelect2Containers, function(idx, value) {
188 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width('auto');
189 $(value).select2('container').width('auto');
190 var curWidth = $(value).select2('container').width();
190 var curWidth = $(value).select2('container').width();
191 if (maxWidth <= curWidth) {
191 if (maxWidth <= curWidth) {
192 maxWidth = curWidth;
192 maxWidth = curWidth;
193 }
193 }
194 $.each(repoSelect2Containers, function(idx, value) {
194 $.each(repoSelect2Containers, function(idx, value) {
195 $(value).select2('container').width(maxWidth + 10);
195 $(value).select2('container').width(maxWidth + 10);
196 });
196 });
197 });
197 });
198 };
198 };
199
199
200 var initRefSelection = function(selectedRef) {
200 var initRefSelection = function(selectedRef) {
201 return function(element, callback) {
201 return function(element, callback) {
202 // translate our select2 id into a text, it's a mapping to show
202 // translate our select2 id into a text, it's a mapping to show
203 // simple label when selecting by internal ID.
203 // simple label when selecting by internal ID.
204 var id, refData;
204 var id, refData;
205 if (selectedRef === undefined) {
205 if (selectedRef === undefined || selectedRef === null) {
206 id = element.val();
206 id = element.val();
207 refData = element.val().split(':');
207 refData = element.val().split(':');
208
209 if (refData.length !== 3){
210 refData = ["", "", ""]
211 }
208 } else {
212 } else {
209 id = selectedRef;
213 id = selectedRef;
210 refData = selectedRef.split(':');
214 refData = selectedRef.split(':');
211 }
215 }
212
216
213 var text = refData[1];
217 var text = refData[1];
214 if (refData[0] === 'rev') {
218 if (refData[0] === 'rev') {
215 text = text.substring(0, 12);
219 text = text.substring(0, 12);
216 }
220 }
217
221
218 var data = {id: id, text: text};
222 var data = {id: id, text: text};
219
220 callback(data);
223 callback(data);
221 };
224 };
222 };
225 };
223
226
224 var formatRefSelection = function(item) {
227 var formatRefSelection = function(item) {
225 var prefix = '';
228 var prefix = '';
226 var refData = item.id.split(':');
229 var refData = item.id.split(':');
227 if (refData[0] === 'branch') {
230 if (refData[0] === 'branch') {
228 prefix = '<i class="icon-branch"></i>';
231 prefix = '<i class="icon-branch"></i>';
229 }
232 }
230 else if (refData[0] === 'book') {
233 else if (refData[0] === 'book') {
231 prefix = '<i class="icon-bookmark"></i>';
234 prefix = '<i class="icon-bookmark"></i>';
232 }
235 }
233 else if (refData[0] === 'tag') {
236 else if (refData[0] === 'tag') {
234 prefix = '<i class="icon-tag"></i>';
237 prefix = '<i class="icon-tag"></i>';
235 }
238 }
236
239
237 var originalOption = item.element;
240 var originalOption = item.element;
238 return prefix + item.text;
241 return prefix + item.text;
239 };
242 };
240
243
241 // custom code mirror
244 // custom code mirror
242 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
245 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
243
246
244 reviewersController = new ReviewersController();
247 reviewersController = new ReviewersController();
245
248
246 var queryTargetRepo = function(self, query) {
249 var queryTargetRepo = function(self, query) {
247 // cache ALL results if query is empty
250 // cache ALL results if query is empty
248 var cacheKey = query.term || '__';
251 var cacheKey = query.term || '__';
249 var cachedData = self.cachedDataSource[cacheKey];
252 var cachedData = self.cachedDataSource[cacheKey];
250
253
251 if (cachedData) {
254 if (cachedData) {
252 query.callback({results: cachedData.results});
255 query.callback({results: cachedData.results});
253 } else {
256 } else {
254 $.ajax({
257 $.ajax({
255 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
258 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
256 data: {query: query.term},
259 data: {query: query.term},
257 dataType: 'json',
260 dataType: 'json',
258 type: 'GET',
261 type: 'GET',
259 success: function(data) {
262 success: function(data) {
260 self.cachedDataSource[cacheKey] = data;
263 self.cachedDataSource[cacheKey] = data;
261 query.callback({results: data.results});
264 query.callback({results: data.results});
262 },
265 },
263 error: function(data, textStatus, errorThrown) {
266 error: function(data, textStatus, errorThrown) {
264 alert(
267 alert(
265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
268 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
266 }
269 }
267 });
270 });
268 }
271 }
269 };
272 };
270
273
271 var queryTargetRefs = function(initialData, query) {
274 var queryTargetRefs = function(initialData, query) {
272 var data = {results: []};
275 var data = {results: []};
273 // filter initialData
276 // filter initialData
274 $.each(initialData, function() {
277 $.each(initialData, function() {
275 var section = this.text;
278 var section = this.text;
276 var children = [];
279 var children = [];
277 $.each(this.children, function() {
280 $.each(this.children, function() {
278 if (query.term.length === 0 ||
281 if (query.term.length === 0 ||
279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
282 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
280 children.push({'id': this.id, 'text': this.text})
283 children.push({'id': this.id, 'text': this.text})
281 }
284 }
282 });
285 });
283 data.results.push({'text': section, 'children': children})
286 data.results.push({'text': section, 'children': children})
284 });
287 });
285 query.callback({results: data.results});
288 query.callback({results: data.results});
286 };
289 };
287
290
288 var loadRepoRefDiffPreview = function() {
291 var loadRepoRefDiffPreview = function() {
289
292
290 var url_data = {
293 var url_data = {
291 'repo_name': targetRepo(),
294 'repo_name': targetRepo(),
292 'target_repo': sourceRepo(),
295 'target_repo': sourceRepo(),
293 'source_ref': targetRef()[2],
296 'source_ref': targetRef()[2],
294 'source_ref_type': 'rev',
297 'source_ref_type': 'rev',
295 'target_ref': sourceRef()[2],
298 'target_ref': sourceRef()[2],
296 'target_ref_type': 'rev',
299 'target_ref_type': 'rev',
297 'merge': true,
300 'merge': true,
298 '_': Date.now() // bypass browser caching
301 '_': Date.now() // bypass browser caching
299 }; // gather the source/target ref and repo here
302 }; // gather the source/target ref and repo here
300
303
301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
304 if (sourceRef().length !== 3 || targetRef().length !== 3) {
302 prButtonLock(true, "${_('Please select source and target')}");
305 prButtonLock(true, "${_('Please select source and target')}");
303 return;
306 return;
304 }
307 }
305 var url = pyroutes.url('repo_compare', url_data);
308 var url = pyroutes.url('repo_compare', url_data);
306
309
307 // lock PR button, so we cannot send PR before it's calculated
310 // lock PR button, so we cannot send PR before it's calculated
308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
311 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
309
312
310 if (loadRepoRefDiffPreview._currentRequest) {
313 if (loadRepoRefDiffPreview._currentRequest) {
311 loadRepoRefDiffPreview._currentRequest.abort();
314 loadRepoRefDiffPreview._currentRequest.abort();
312 }
315 }
313
316
314 loadRepoRefDiffPreview._currentRequest = $.get(url)
317 loadRepoRefDiffPreview._currentRequest = $.get(url)
315 .error(function(data, textStatus, errorThrown) {
318 .error(function(data, textStatus, errorThrown) {
316 if (textStatus !== 'abort') {
319 if (textStatus !== 'abort') {
317 alert(
320 alert(
318 "Error while processing request.\nError code {0} ({1}).".format(
321 "Error while processing request.\nError code {0} ({1}).".format(
319 data.status, data.statusText));
322 data.status, data.statusText));
320 }
323 }
321
324
322 })
325 })
323 .done(function(data) {
326 .done(function(data) {
324 loadRepoRefDiffPreview._currentRequest = null;
327 loadRepoRefDiffPreview._currentRequest = null;
325 $('#pull_request_overview').html(data);
328 $('#pull_request_overview').html(data);
326
329
327 var commitElements = $(data).find('tr[commit_id]');
330 var commitElements = $(data).find('tr[commit_id]');
328
331
329 var prTitleAndDesc = getTitleAndDescription(
332 var prTitleAndDesc = getTitleAndDescription(
330 sourceRef()[1], commitElements, 5);
333 sourceRef()[1], commitElements, 5);
331
334
332 var title = prTitleAndDesc[0];
335 var title = prTitleAndDesc[0];
333 var proposedDescription = prTitleAndDesc[1];
336 var proposedDescription = prTitleAndDesc[1];
334
337
335 var useGeneratedTitle = (
338 var useGeneratedTitle = (
336 $('#pullrequest_title').hasClass('autogenerated-title') ||
339 $('#pullrequest_title').hasClass('autogenerated-title') ||
337 $('#pullrequest_title').val() === "");
340 $('#pullrequest_title').val() === "");
338
341
339 if (title && useGeneratedTitle) {
342 if (title && useGeneratedTitle) {
340 // use generated title if we haven't specified our own
343 // use generated title if we haven't specified our own
341 $('#pullrequest_title').val(title);
344 $('#pullrequest_title').val(title);
342 $('#pullrequest_title').addClass('autogenerated-title');
345 $('#pullrequest_title').addClass('autogenerated-title');
343
346
344 }
347 }
345
348
346 var useGeneratedDescription = (
349 var useGeneratedDescription = (
347 !codeMirrorInstance._userDefinedDesc ||
350 !codeMirrorInstance._userDefinedDesc ||
348 codeMirrorInstance.getValue() === "");
351 codeMirrorInstance.getValue() === "");
349
352
350 if (proposedDescription && useGeneratedDescription) {
353 if (proposedDescription && useGeneratedDescription) {
351 // set proposed content, if we haven't defined our own,
354 // set proposed content, if we haven't defined our own,
352 // or we don't have description written
355 // or we don't have description written
353 codeMirrorInstance._userDefinedDesc = false; // reset state
356 codeMirrorInstance._userDefinedDesc = false; // reset state
354 codeMirrorInstance.setValue(proposedDescription);
357 codeMirrorInstance.setValue(proposedDescription);
355 }
358 }
356
359
357 var msg = '';
360 var msg = '';
358 if (commitElements.length === 1) {
361 if (commitElements.length === 1) {
359 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
360 } else {
363 } else {
361 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
362 }
365 }
363
366
364 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
365
368
366 if (commitElements.length) {
369 if (commitElements.length) {
367 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
368 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
369 }
372 }
370 else {
373 else {
371 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
372 }
375 }
373
376
374
377
375 });
378 });
376 };
379 };
377
380
378 var Select2Box = function(element, overrides) {
381 var Select2Box = function(element, overrides) {
379 var globalDefaults = {
382 var globalDefaults = {
380 dropdownAutoWidth: true,
383 dropdownAutoWidth: true,
381 containerCssClass: "drop-menu",
384 containerCssClass: "drop-menu",
382 dropdownCssClass: "drop-menu-dropdown"
385 dropdownCssClass: "drop-menu-dropdown"
383 };
386 };
384
387
385 var initSelect2 = function(defaultOptions) {
388 var initSelect2 = function(defaultOptions) {
386 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
387 element.select2(options);
390 element.select2(options);
388 };
391 };
389
392
390 return {
393 return {
391 initRef: function() {
394 initRef: function() {
392 var defaultOptions = {
395 var defaultOptions = {
393 minimumResultsForSearch: 5,
396 minimumResultsForSearch: 5,
394 formatSelection: formatRefSelection
397 formatSelection: formatRefSelection
395 };
398 };
396
399
397 initSelect2(defaultOptions);
400 initSelect2(defaultOptions);
398 },
401 },
399
402
400 initRepo: function(defaultValue, readOnly) {
403 initRepo: function(defaultValue, readOnly) {
401 var defaultOptions = {
404 var defaultOptions = {
402 initSelection : function (element, callback) {
405 initSelection : function (element, callback) {
403 var data = {id: defaultValue, text: defaultValue};
406 var data = {id: defaultValue, text: defaultValue};
404 callback(data);
407 callback(data);
405 }
408 }
406 };
409 };
407
410
408 initSelect2(defaultOptions);
411 initSelect2(defaultOptions);
409
412
410 element.select2('val', defaultSourceRepo);
413 element.select2('val', defaultSourceRepo);
411 if (readOnly === true) {
414 if (readOnly === true) {
412 element.select2('readonly', true);
415 element.select2('readonly', true);
413 }
416 }
414 }
417 }
415 };
418 };
416 };
419 };
417
420
418 var initTargetRefs = function(refsData, selectedRef){
421 var initTargetRefs = function(refsData, selectedRef) {
422
419 Select2Box($targetRef, {
423 Select2Box($targetRef, {
424 placeholder: "${_('Select commit reference')}",
420 query: function(query) {
425 query: function(query) {
421 queryTargetRefs(refsData, query);
426 queryTargetRefs(refsData, query);
422 },
427 },
423 initSelection : initRefSelection(selectedRef)
428 initSelection : initRefSelection(selectedRef)
424 }).initRef();
429 }).initRef();
425
430
426 if (!(selectedRef === undefined)) {
431 if (!(selectedRef === undefined)) {
427 $targetRef.select2('val', selectedRef);
432 $targetRef.select2('val', selectedRef);
428 }
433 }
429 };
434 };
430
435
431 var targetRepoChanged = function(repoData) {
436 var targetRepoChanged = function(repoData) {
432 // generate new DESC of target repo displayed next to select
437 // generate new DESC of target repo displayed next to select
433 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
434 $('#target_repo_desc').html(
439 $('#target_repo_desc').html(
435 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
436 );
441 );
437
442
438 // generate dynamic select2 for refs.
443 // generate dynamic select2 for refs.
439 initTargetRefs(repoData['refs']['select2_refs'],
444 initTargetRefs(repoData['refs']['select2_refs'],
440 repoData['refs']['selected_ref']);
445 repoData['refs']['selected_ref']);
441
446
442 };
447 };
443
448
444 var sourceRefSelect2 = Select2Box($sourceRef, {
449 var sourceRefSelect2 = Select2Box($sourceRef, {
445 placeholder: "${_('Select commit reference')}",
450 placeholder: "${_('Select commit reference')}",
446 query: function(query) {
451 query: function(query) {
447 var initialData = defaultSourceRepoData['refs']['select2_refs'];
452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
448 queryTargetRefs(initialData, query)
453 queryTargetRefs(initialData, query)
449 },
454 },
450 initSelection: initRefSelection()
455 initSelection: initRefSelection()
451 }
456 }
452 );
457 );
453
458
454 var sourceRepoSelect2 = Select2Box($sourceRepo, {
459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
455 query: function(query) {}
460 query: function(query) {}
456 });
461 });
457
462
458 var targetRepoSelect2 = Select2Box($targetRepo, {
463 var targetRepoSelect2 = Select2Box($targetRepo, {
459 cachedDataSource: {},
464 cachedDataSource: {},
460 query: $.debounce(250, function(query) {
465 query: $.debounce(250, function(query) {
461 queryTargetRepo(this, query);
466 queryTargetRepo(this, query);
462 }),
467 }),
463 formatResult: formatResult
468 formatResult: formatResult
464 });
469 });
465
470
466 sourceRefSelect2.initRef();
471 sourceRefSelect2.initRef();
467
472
468 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
469
474
470 targetRepoSelect2.initRepo(defaultTargetRepo, false);
475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
471
476
472 $sourceRef.on('change', function(e){
477 $sourceRef.on('change', function(e){
473 loadRepoRefDiffPreview();
478 loadRepoRefDiffPreview();
474 reviewersController.loadDefaultReviewers(
479 reviewersController.loadDefaultReviewers(
475 sourceRepo(), sourceRef(), targetRepo(), targetRef());
480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
476 });
481 });
477
482
478 $targetRef.on('change', function(e){
483 $targetRef.on('change', function(e){
479 loadRepoRefDiffPreview();
484 loadRepoRefDiffPreview();
480 reviewersController.loadDefaultReviewers(
485 reviewersController.loadDefaultReviewers(
481 sourceRepo(), sourceRef(), targetRepo(), targetRef());
486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
482 });
487 });
483
488
484 $targetRepo.on('change', function(e){
489 $targetRepo.on('change', function(e){
485 var repoName = $(this).val();
490 var repoName = $(this).val();
486 calculateContainerWidth();
491 calculateContainerWidth();
487 $targetRef.select2('destroy');
492 $targetRef.select2('destroy');
488 $('#target_ref_loading').show();
493 $('#target_ref_loading').show();
489
494
490 $.ajax({
495 $.ajax({
491 url: pyroutes.url('pullrequest_repo_refs',
496 url: pyroutes.url('pullrequest_repo_refs',
492 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
493 data: {},
498 data: {},
494 dataType: 'json',
499 dataType: 'json',
495 type: 'GET',
500 type: 'GET',
496 success: function(data) {
501 success: function(data) {
497 $('#target_ref_loading').hide();
502 $('#target_ref_loading').hide();
498 targetRepoChanged(data);
503 targetRepoChanged(data);
499 loadRepoRefDiffPreview();
504 loadRepoRefDiffPreview();
500 },
505 },
501 error: function(data, textStatus, errorThrown) {
506 error: function(data, textStatus, errorThrown) {
502 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
503 }
508 }
504 })
509 })
505
510
506 });
511 });
507
512
508 prButtonLock(true, "${_('Please select source and target')}", 'all');
513 prButtonLock(true, "${_('Please select source and target')}", 'all');
509
514
510 // auto-load on init, the target refs select2
515 // auto-load on init, the target refs select2
511 calculateContainerWidth();
516 calculateContainerWidth();
512 targetRepoChanged(defaultTargetRepoData);
517 targetRepoChanged(defaultTargetRepoData);
513
518
514 $('#pullrequest_title').on('keyup', function(e){
519 $('#pullrequest_title').on('keyup', function(e){
515 $(this).removeClass('autogenerated-title');
520 $(this).removeClass('autogenerated-title');
516 });
521 });
517
522
518 % if c.default_source_ref:
523 % if c.default_source_ref:
519 // in case we have a pre-selected value, use it now
524 // in case we have a pre-selected value, use it now
520 $sourceRef.select2('val', '${c.default_source_ref}');
525 $sourceRef.select2('val', '${c.default_source_ref}');
526 // diff preview load
521 loadRepoRefDiffPreview();
527 loadRepoRefDiffPreview();
528 // default reviewers
522 reviewersController.loadDefaultReviewers(
529 reviewersController.loadDefaultReviewers(
523 sourceRepo(), sourceRef(), targetRepo(), targetRef());
530 sourceRepo(), sourceRef(), targetRepo(), targetRef());
524 % endif
531 % endif
525
532
526 ReviewerAutoComplete('#user');
533 ReviewerAutoComplete('#user');
527 });
534 });
528 </script>
535 </script>
529
536
530 </%def>
537 </%def>
General Comments 0
You need to be logged in to leave comments. Login now