##// END OF EJS Templates
pull-requests: lock submit on pull request to prevent double submission on fast click.
marcink -
r2806:5a43c6a7 default
parent child Browse files
Show More
@@ -1,593 +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 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#save').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(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) {
220 if (sourceRef.length !== 3 || targetRef.length !== 3) {
221 // don't load defaults in case we're missing some refs...
221 // don't load defaults in case we're missing some refs...
222 $('.calculate-reviewers').hide();
222 $('.calculate-reviewers').hide();
223 return
223 return
224 }
224 }
225
225
226 var url = pyroutes.url('repo_default_reviewers_data',
226 var url = pyroutes.url('repo_default_reviewers_data',
227 {
227 {
228 'repo_name': templateContext.repo_name,
228 'repo_name': templateContext.repo_name,
229 'source_repo': sourceRepo,
229 'source_repo': sourceRepo,
230 'source_ref': sourceRef[2],
230 'source_ref': sourceRef[2],
231 'target_repo': targetRepo,
231 'target_repo': targetRepo,
232 'target_ref': targetRef[2]
232 'target_ref': targetRef[2]
233 });
233 });
234
234
235 self.currentRequest = $.get(url)
235 self.currentRequest = $.get(url)
236 .done(function(data) {
236 .done(function(data) {
237 self.currentRequest = null;
237 self.currentRequest = null;
238
238
239 // review rules
239 // review rules
240 self.loadReviewRules(data);
240 self.loadReviewRules(data);
241
241
242 for (var i = 0; i < data.reviewers.length; i++) {
242 for (var i = 0; i < data.reviewers.length; i++) {
243 var reviewer = data.reviewers[i];
243 var reviewer = data.reviewers[i];
244 self.addReviewMember(
244 self.addReviewMember(
245 reviewer, reviewer.reasons, reviewer.mandatory);
245 reviewer, reviewer.reasons, reviewer.mandatory);
246 }
246 }
247 $('.calculate-reviewers').hide();
247 $('.calculate-reviewers').hide();
248 prButtonLock(false, null, 'reviewers');
248 prButtonLock(false, null, 'reviewers');
249 $('#user').show(); // show user autocomplete after load
249 $('#user').show(); // show user autocomplete after load
250 });
250 });
251 };
251 };
252
252
253 // check those, refactor
253 // check those, refactor
254 this.removeReviewMember = function(reviewer_id, mark_delete) {
254 this.removeReviewMember = function(reviewer_id, mark_delete) {
255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
256
256
257 if(typeof(mark_delete) === undefined){
257 if(typeof(mark_delete) === undefined){
258 mark_delete = false;
258 mark_delete = false;
259 }
259 }
260
260
261 if(mark_delete === true){
261 if(mark_delete === true){
262 if (reviewer){
262 if (reviewer){
263 // now delete the input
263 // now delete the input
264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
265 // mark as to-delete
265 // mark as to-delete
266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
267 obj.addClass('to-delete');
267 obj.addClass('to-delete');
268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
269 }
269 }
270 }
270 }
271 else{
271 else{
272 $('#reviewer_{0}'.format(reviewer_id)).remove();
272 $('#reviewer_{0}'.format(reviewer_id)).remove();
273 }
273 }
274 };
274 };
275 this.reviewMemberEntry = function() {
275 this.reviewMemberEntry = function() {
276
276
277 };
277 };
278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
279 var members = self.$reviewMembers.get(0);
279 var members = self.$reviewMembers.get(0);
280 var id = reviewer_obj.user_id;
280 var id = reviewer_obj.user_id;
281 var username = reviewer_obj.username;
281 var username = reviewer_obj.username;
282
282
283 var reasons = reasons || [];
283 var reasons = reasons || [];
284 var mandatory = mandatory || false;
284 var mandatory = mandatory || false;
285
285
286 // 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
287 var currentIds = [];
287 var currentIds = [];
288 var _els = self.$reviewMembers.find('li').toArray();
288 var _els = self.$reviewMembers.find('li').toArray();
289 for (el in _els){
289 for (el in _els){
290 currentIds.push(_els[el].id)
290 currentIds.push(_els[el].id)
291 }
291 }
292
292
293 var userAllowedReview = function(userId) {
293 var userAllowedReview = function(userId) {
294 var allowed = true;
294 var allowed = true;
295 $.each(self.forbidReviewUsers, function(index, member_data) {
295 $.each(self.forbidReviewUsers, function(index, member_data) {
296 if (parseInt(userId) === member_data['user_id']) {
296 if (parseInt(userId) === member_data['user_id']) {
297 allowed = false;
297 allowed = false;
298 return false // breaks the loop
298 return false // breaks the loop
299 }
299 }
300 });
300 });
301 return allowed
301 return allowed
302 };
302 };
303
303
304 var userAllowed = userAllowedReview(id);
304 var userAllowed = userAllowedReview(id);
305 if (!userAllowed){
305 if (!userAllowed){
306 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));
307 } else {
307 } else {
308 // only add if it's not there
308 // only add if it's not there
309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
310
310
311 if (alreadyReviewer) {
311 if (alreadyReviewer) {
312 alert(_gettext('User `{0}` already in reviewers').format(username));
312 alert(_gettext('User `{0}` already in reviewers').format(username));
313 } else {
313 } else {
314 members.innerHTML += renderTemplate('reviewMemberEntry', {
314 members.innerHTML += renderTemplate('reviewMemberEntry', {
315 'member': reviewer_obj,
315 'member': reviewer_obj,
316 'mandatory': mandatory,
316 'mandatory': mandatory,
317 'allowed_to_update': true,
317 'allowed_to_update': true,
318 'review_status': 'not_reviewed',
318 'review_status': 'not_reviewed',
319 'review_status_label': _gettext('Not Reviewed'),
319 'review_status_label': _gettext('Not Reviewed'),
320 'reasons': reasons,
320 'reasons': reasons,
321 'create': true
321 'create': true
322 });
322 });
323 }
323 }
324 }
324 }
325
325
326 };
326 };
327
327
328 this.updateReviewers = function(repo_name, pull_request_id){
328 this.updateReviewers = function(repo_name, pull_request_id){
329 var postData = $('#reviewers input').serialize();
329 var postData = $('#reviewers input').serialize();
330 _updatePullRequest(repo_name, pull_request_id, postData);
330 _updatePullRequest(repo_name, pull_request_id, postData);
331 };
331 };
332
332
333 };
333 };
334
334
335
335
336 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
336 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
337 var url = pyroutes.url(
337 var url = pyroutes.url(
338 'pullrequest_update',
338 'pullrequest_update',
339 {"repo_name": repo_name, "pull_request_id": pull_request_id});
339 {"repo_name": repo_name, "pull_request_id": pull_request_id});
340 if (typeof postData === 'string' ) {
340 if (typeof postData === 'string' ) {
341 postData += '&csrf_token=' + CSRF_TOKEN;
341 postData += '&csrf_token=' + CSRF_TOKEN;
342 } else {
342 } else {
343 postData.csrf_token = CSRF_TOKEN;
343 postData.csrf_token = CSRF_TOKEN;
344 }
344 }
345 var success = function(o) {
345 var success = function(o) {
346 window.location.reload();
346 window.location.reload();
347 };
347 };
348 ajaxPOST(url, postData, success);
348 ajaxPOST(url, postData, success);
349 };
349 };
350
350
351 /**
351 /**
352 * PULL REQUEST update commits
352 * PULL REQUEST update commits
353 */
353 */
354 var updateCommits = function(repo_name, pull_request_id) {
354 var updateCommits = function(repo_name, pull_request_id) {
355 var postData = {
355 var postData = {
356 'update_commits': true};
356 'update_commits': true};
357 _updatePullRequest(repo_name, pull_request_id, postData);
357 _updatePullRequest(repo_name, pull_request_id, postData);
358 };
358 };
359
359
360
360
361 /**
361 /**
362 * PULL REQUEST edit info
362 * PULL REQUEST edit info
363 */
363 */
364 var editPullRequest = function(repo_name, pull_request_id, title, description) {
364 var editPullRequest = function(repo_name, pull_request_id, title, description) {
365 var url = pyroutes.url(
365 var url = pyroutes.url(
366 'pullrequest_update',
366 'pullrequest_update',
367 {"repo_name": repo_name, "pull_request_id": pull_request_id});
367 {"repo_name": repo_name, "pull_request_id": pull_request_id});
368
368
369 var postData = {
369 var postData = {
370 'title': title,
370 'title': title,
371 'description': description,
371 'description': description,
372 'edit_pull_request': true,
372 'edit_pull_request': true,
373 'csrf_token': CSRF_TOKEN
373 'csrf_token': CSRF_TOKEN
374 };
374 };
375 var success = function(o) {
375 var success = function(o) {
376 window.location.reload();
376 window.location.reload();
377 };
377 };
378 ajaxPOST(url, postData, success);
378 ajaxPOST(url, postData, success);
379 };
379 };
380
380
381 var initPullRequestsCodeMirror = function (textAreaId) {
381 var initPullRequestsCodeMirror = function (textAreaId) {
382 var ta = $(textAreaId).get(0);
382 var ta = $(textAreaId).get(0);
383 var initialHeight = '100px';
383 var initialHeight = '100px';
384
384
385 // default options
385 // default options
386 var codeMirrorOptions = {
386 var codeMirrorOptions = {
387 mode: "text",
387 mode: "text",
388 lineNumbers: false,
388 lineNumbers: false,
389 indentUnit: 4,
389 indentUnit: 4,
390 theme: 'rc-input'
390 theme: 'rc-input'
391 };
391 };
392
392
393 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
393 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
394 // marker for manually set description
394 // marker for manually set description
395 codeMirrorInstance._userDefinedDesc = false;
395 codeMirrorInstance._userDefinedDesc = false;
396 codeMirrorInstance.setSize(null, initialHeight);
396 codeMirrorInstance.setSize(null, initialHeight);
397 codeMirrorInstance.on("change", function(instance, changeObj) {
397 codeMirrorInstance.on("change", function(instance, changeObj) {
398 var height = initialHeight;
398 var height = initialHeight;
399 var lines = instance.lineCount();
399 var lines = instance.lineCount();
400 if (lines > 6 && lines < 20) {
400 if (lines > 6 && lines < 20) {
401 height = "auto"
401 height = "auto"
402 }
402 }
403 else if (lines >= 20) {
403 else if (lines >= 20) {
404 height = 20 * 15;
404 height = 20 * 15;
405 }
405 }
406 instance.setSize(null, height);
406 instance.setSize(null, height);
407
407
408 // 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
409 changeOrigin = changeObj.origin;
409 changeOrigin = changeObj.origin;
410
410
411 if (changeOrigin === "setValue") {
411 if (changeOrigin === "setValue") {
412 cmLog.debug('Change triggered by setValue');
412 cmLog.debug('Change triggered by setValue');
413 }
413 }
414 else {
414 else {
415 cmLog.debug('user triggered change !');
415 cmLog.debug('user triggered change !');
416 // set special marker to indicate user has created an input.
416 // set special marker to indicate user has created an input.
417 instance._userDefinedDesc = true;
417 instance._userDefinedDesc = true;
418 }
418 }
419
419
420 });
420 });
421
421
422 return codeMirrorInstance
422 return codeMirrorInstance
423 };
423 };
424
424
425 /**
425 /**
426 * Reviewer autocomplete
426 * Reviewer autocomplete
427 */
427 */
428 var ReviewerAutoComplete = function(inputId) {
428 var ReviewerAutoComplete = function(inputId) {
429 $(inputId).autocomplete({
429 $(inputId).autocomplete({
430 serviceUrl: pyroutes.url('user_autocomplete_data'),
430 serviceUrl: pyroutes.url('user_autocomplete_data'),
431 minChars:2,
431 minChars:2,
432 maxHeight:400,
432 maxHeight:400,
433 deferRequestBy: 300, //miliseconds
433 deferRequestBy: 300, //miliseconds
434 showNoSuggestionNotice: true,
434 showNoSuggestionNotice: true,
435 tabDisabled: true,
435 tabDisabled: true,
436 autoSelectFirst: true,
436 autoSelectFirst: true,
437 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 },
438 formatResult: autocompleteFormatResult,
438 formatResult: autocompleteFormatResult,
439 lookupFilter: autocompleteFilterResult,
439 lookupFilter: autocompleteFilterResult,
440 onSelect: function(element, data) {
440 onSelect: function(element, data) {
441 var mandatory = false;
441 var mandatory = false;
442 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)];
443
443
444 // add whole user groups
444 // add whole user groups
445 if (data.value_type == 'user_group') {
445 if (data.value_type == 'user_group') {
446 reasons.push(_gettext('member of "{0}"').format(data.value_display));
446 reasons.push(_gettext('member of "{0}"').format(data.value_display));
447
447
448 $.each(data.members, function(index, member_data) {
448 $.each(data.members, function(index, member_data) {
449 var reviewer = member_data;
449 var reviewer = member_data;
450 reviewer['user_id'] = member_data['id'];
450 reviewer['user_id'] = member_data['id'];
451 reviewer['gravatar_link'] = member_data['icon_link'];
451 reviewer['gravatar_link'] = member_data['icon_link'];
452 reviewer['user_link'] = member_data['profile_link'];
452 reviewer['user_link'] = member_data['profile_link'];
453 reviewer['rules'] = [];
453 reviewer['rules'] = [];
454 reviewersController.addReviewMember(reviewer, reasons, mandatory);
454 reviewersController.addReviewMember(reviewer, reasons, mandatory);
455 })
455 })
456 }
456 }
457 // add single user
457 // add single user
458 else {
458 else {
459 var reviewer = data;
459 var reviewer = data;
460 reviewer['user_id'] = data['id'];
460 reviewer['user_id'] = data['id'];
461 reviewer['gravatar_link'] = data['icon_link'];
461 reviewer['gravatar_link'] = data['icon_link'];
462 reviewer['user_link'] = data['profile_link'];
462 reviewer['user_link'] = data['profile_link'];
463 reviewer['rules'] = [];
463 reviewer['rules'] = [];
464 reviewersController.addReviewMember(reviewer, reasons, mandatory);
464 reviewersController.addReviewMember(reviewer, reasons, mandatory);
465 }
465 }
466
466
467 $(inputId).val('');
467 $(inputId).val('');
468 }
468 }
469 });
469 });
470 };
470 };
471
471
472
472
473 VersionController = function () {
473 VersionController = function () {
474 var self = this;
474 var self = this;
475 this.$verSource = $('input[name=ver_source]');
475 this.$verSource = $('input[name=ver_source]');
476 this.$verTarget = $('input[name=ver_target]');
476 this.$verTarget = $('input[name=ver_target]');
477 this.$showVersionDiff = $('#show-version-diff');
477 this.$showVersionDiff = $('#show-version-diff');
478
478
479 this.adjustRadioSelectors = function (curNode) {
479 this.adjustRadioSelectors = function (curNode) {
480 var getVal = function (item) {
480 var getVal = function (item) {
481 if (item == 'latest') {
481 if (item == 'latest') {
482 return Number.MAX_SAFE_INTEGER
482 return Number.MAX_SAFE_INTEGER
483 }
483 }
484 else {
484 else {
485 return parseInt(item)
485 return parseInt(item)
486 }
486 }
487 };
487 };
488
488
489 var curVal = getVal($(curNode).val());
489 var curVal = getVal($(curNode).val());
490 var cleared = false;
490 var cleared = false;
491
491
492 $.each(self.$verSource, function (index, value) {
492 $.each(self.$verSource, function (index, value) {
493 var elVal = getVal($(value).val());
493 var elVal = getVal($(value).val());
494
494
495 if (elVal > curVal) {
495 if (elVal > curVal) {
496 if ($(value).is(':checked')) {
496 if ($(value).is(':checked')) {
497 cleared = true;
497 cleared = true;
498 }
498 }
499 $(value).attr('disabled', 'disabled');
499 $(value).attr('disabled', 'disabled');
500 $(value).removeAttr('checked');
500 $(value).removeAttr('checked');
501 $(value).css({'opacity': 0.1});
501 $(value).css({'opacity': 0.1});
502 }
502 }
503 else {
503 else {
504 $(value).css({'opacity': 1});
504 $(value).css({'opacity': 1});
505 $(value).removeAttr('disabled');
505 $(value).removeAttr('disabled');
506 }
506 }
507 });
507 });
508
508
509 if (cleared) {
509 if (cleared) {
510 // 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.
511 $(this.$verSource).filter('[value={0}]'.format(
511 $(this.$verSource).filter('[value={0}]'.format(
512 curVal)).attr('checked', 'checked');
512 curVal)).attr('checked', 'checked');
513 }
513 }
514
514
515 self.setLockAction(false,
515 self.setLockAction(false,
516 $(curNode).data('verPos'),
516 $(curNode).data('verPos'),
517 $(this.$verSource).filter(':checked').data('verPos')
517 $(this.$verSource).filter(':checked').data('verPos')
518 );
518 );
519 };
519 };
520
520
521
521
522 this.attachVersionListener = function () {
522 this.attachVersionListener = function () {
523 self.$verTarget.change(function (e) {
523 self.$verTarget.change(function (e) {
524 self.adjustRadioSelectors(this)
524 self.adjustRadioSelectors(this)
525 });
525 });
526 self.$verSource.change(function (e) {
526 self.$verSource.change(function (e) {
527 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
527 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
528 });
528 });
529 };
529 };
530
530
531 this.init = function () {
531 this.init = function () {
532
532
533 var curNode = self.$verTarget.filter(':checked');
533 var curNode = self.$verTarget.filter(':checked');
534 self.adjustRadioSelectors(curNode);
534 self.adjustRadioSelectors(curNode);
535 self.setLockAction(true);
535 self.setLockAction(true);
536 self.attachVersionListener();
536 self.attachVersionListener();
537
537
538 };
538 };
539
539
540 this.setLockAction = function (state, selectedVersion, otherVersion) {
540 this.setLockAction = function (state, selectedVersion, otherVersion) {
541 var $showVersionDiff = this.$showVersionDiff;
541 var $showVersionDiff = this.$showVersionDiff;
542
542
543 if (state) {
543 if (state) {
544 $showVersionDiff.attr('disabled', 'disabled');
544 $showVersionDiff.attr('disabled', 'disabled');
545 $showVersionDiff.addClass('disabled');
545 $showVersionDiff.addClass('disabled');
546 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
546 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
547 }
547 }
548 else {
548 else {
549 $showVersionDiff.removeAttr('disabled');
549 $showVersionDiff.removeAttr('disabled');
550 $showVersionDiff.removeClass('disabled');
550 $showVersionDiff.removeClass('disabled');
551
551
552 if (selectedVersion == otherVersion) {
552 if (selectedVersion == otherVersion) {
553 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
553 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
554 } else {
554 } else {
555 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
555 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
556 }
556 }
557 }
557 }
558
558
559 };
559 };
560
560
561 this.showVersionDiff = function () {
561 this.showVersionDiff = function () {
562 var target = self.$verTarget.filter(':checked');
562 var target = self.$verTarget.filter(':checked');
563 var source = self.$verSource.filter(':checked');
563 var source = self.$verSource.filter(':checked');
564
564
565 if (target.val() && source.val()) {
565 if (target.val() && source.val()) {
566 var params = {
566 var params = {
567 'pull_request_id': templateContext.pull_request_data.pull_request_id,
567 'pull_request_id': templateContext.pull_request_data.pull_request_id,
568 'repo_name': templateContext.repo_name,
568 'repo_name': templateContext.repo_name,
569 'version': target.val(),
569 'version': target.val(),
570 'from_version': source.val()
570 'from_version': source.val()
571 };
571 };
572 window.location = pyroutes.url('pullrequest_show', params)
572 window.location = pyroutes.url('pullrequest_show', params)
573 }
573 }
574
574
575 return false;
575 return false;
576 };
576 };
577
577
578 this.toggleVersionView = function (elem) {
578 this.toggleVersionView = function (elem) {
579
579
580 if (this.$showVersionDiff.is(':visible')) {
580 if (this.$showVersionDiff.is(':visible')) {
581 $('.version-pr').hide();
581 $('.version-pr').hide();
582 this.$showVersionDiff.hide();
582 this.$showVersionDiff.hide();
583 $(elem).html($(elem).data('toggleOn'))
583 $(elem).html($(elem).data('toggleOn'))
584 } else {
584 } else {
585 $('.version-pr').show();
585 $('.version-pr').show();
586 this.$showVersionDiff.show();
586 this.$showVersionDiff.show();
587 $(elem).html($(elem).data('toggleOff'))
587 $(elem).html($(elem).data('toggleOff'))
588 }
588 }
589
589
590 return false
590 return false
591 }
591 }
592
592
593 }; No newline at end of file
593 };
@@ -1,537 +1,542 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 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
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 $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
174 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $sourceRepo = $('#source_repo', $pullRequestForm);
175 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $targetRepo = $('#target_repo', $pullRequestForm);
176 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $sourceRef = $('#source_ref', $pullRequestForm);
177 var $targetRef = $('#target_ref', $pullRequestForm);
178 var $targetRef = $('#target_ref', $pullRequestForm);
178
179
179 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
180 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
181
182
182 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRepo = function() { return $targetRepo.eq(0).val() };
183 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
184
185
185 var calculateContainerWidth = function() {
186 var calculateContainerWidth = function() {
186 var maxWidth = 0;
187 var maxWidth = 0;
187 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 var repoSelect2Containers = ['#source_repo', '#target_repo'];
188 $.each(repoSelect2Containers, function(idx, value) {
189 $.each(repoSelect2Containers, function(idx, value) {
189 $(value).select2('container').width('auto');
190 $(value).select2('container').width('auto');
190 var curWidth = $(value).select2('container').width();
191 var curWidth = $(value).select2('container').width();
191 if (maxWidth <= curWidth) {
192 if (maxWidth <= curWidth) {
192 maxWidth = curWidth;
193 maxWidth = curWidth;
193 }
194 }
194 $.each(repoSelect2Containers, function(idx, value) {
195 $.each(repoSelect2Containers, function(idx, value) {
195 $(value).select2('container').width(maxWidth + 10);
196 $(value).select2('container').width(maxWidth + 10);
196 });
197 });
197 });
198 });
198 };
199 };
199
200
200 var initRefSelection = function(selectedRef) {
201 var initRefSelection = function(selectedRef) {
201 return function(element, callback) {
202 return function(element, callback) {
202 // translate our select2 id into a text, it's a mapping to show
203 // translate our select2 id into a text, it's a mapping to show
203 // simple label when selecting by internal ID.
204 // simple label when selecting by internal ID.
204 var id, refData;
205 var id, refData;
205 if (selectedRef === undefined || selectedRef === null) {
206 if (selectedRef === undefined || selectedRef === null) {
206 id = element.val();
207 id = element.val();
207 refData = element.val().split(':');
208 refData = element.val().split(':');
208
209
209 if (refData.length !== 3){
210 if (refData.length !== 3){
210 refData = ["", "", ""]
211 refData = ["", "", ""]
211 }
212 }
212 } else {
213 } else {
213 id = selectedRef;
214 id = selectedRef;
214 refData = selectedRef.split(':');
215 refData = selectedRef.split(':');
215 }
216 }
216
217
217 var text = refData[1];
218 var text = refData[1];
218 if (refData[0] === 'rev') {
219 if (refData[0] === 'rev') {
219 text = text.substring(0, 12);
220 text = text.substring(0, 12);
220 }
221 }
221
222
222 var data = {id: id, text: text};
223 var data = {id: id, text: text};
223 callback(data);
224 callback(data);
224 };
225 };
225 };
226 };
226
227
227 var formatRefSelection = function(item) {
228 var formatRefSelection = function(item) {
228 var prefix = '';
229 var prefix = '';
229 var refData = item.id.split(':');
230 var refData = item.id.split(':');
230 if (refData[0] === 'branch') {
231 if (refData[0] === 'branch') {
231 prefix = '<i class="icon-branch"></i>';
232 prefix = '<i class="icon-branch"></i>';
232 }
233 }
233 else if (refData[0] === 'book') {
234 else if (refData[0] === 'book') {
234 prefix = '<i class="icon-bookmark"></i>';
235 prefix = '<i class="icon-bookmark"></i>';
235 }
236 }
236 else if (refData[0] === 'tag') {
237 else if (refData[0] === 'tag') {
237 prefix = '<i class="icon-tag"></i>';
238 prefix = '<i class="icon-tag"></i>';
238 }
239 }
239
240
240 var originalOption = item.element;
241 var originalOption = item.element;
241 return prefix + item.text;
242 return prefix + item.text;
242 };
243 };
243
244
244 // custom code mirror
245 // custom code mirror
245 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
246 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
246
247
247 reviewersController = new ReviewersController();
248 reviewersController = new ReviewersController();
248
249
249 var queryTargetRepo = function(self, query) {
250 var queryTargetRepo = function(self, query) {
250 // cache ALL results if query is empty
251 // cache ALL results if query is empty
251 var cacheKey = query.term || '__';
252 var cacheKey = query.term || '__';
252 var cachedData = self.cachedDataSource[cacheKey];
253 var cachedData = self.cachedDataSource[cacheKey];
253
254
254 if (cachedData) {
255 if (cachedData) {
255 query.callback({results: cachedData.results});
256 query.callback({results: cachedData.results});
256 } else {
257 } else {
257 $.ajax({
258 $.ajax({
258 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
259 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
259 data: {query: query.term},
260 data: {query: query.term},
260 dataType: 'json',
261 dataType: 'json',
261 type: 'GET',
262 type: 'GET',
262 success: function(data) {
263 success: function(data) {
263 self.cachedDataSource[cacheKey] = data;
264 self.cachedDataSource[cacheKey] = data;
264 query.callback({results: data.results});
265 query.callback({results: data.results});
265 },
266 },
266 error: function(data, textStatus, errorThrown) {
267 error: function(data, textStatus, errorThrown) {
267 alert(
268 alert(
268 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
269 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
269 }
270 }
270 });
271 });
271 }
272 }
272 };
273 };
273
274
274 var queryTargetRefs = function(initialData, query) {
275 var queryTargetRefs = function(initialData, query) {
275 var data = {results: []};
276 var data = {results: []};
276 // filter initialData
277 // filter initialData
277 $.each(initialData, function() {
278 $.each(initialData, function() {
278 var section = this.text;
279 var section = this.text;
279 var children = [];
280 var children = [];
280 $.each(this.children, function() {
281 $.each(this.children, function() {
281 if (query.term.length === 0 ||
282 if (query.term.length === 0 ||
282 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
283 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
283 children.push({'id': this.id, 'text': this.text})
284 children.push({'id': this.id, 'text': this.text})
284 }
285 }
285 });
286 });
286 data.results.push({'text': section, 'children': children})
287 data.results.push({'text': section, 'children': children})
287 });
288 });
288 query.callback({results: data.results});
289 query.callback({results: data.results});
289 };
290 };
290
291
291 var loadRepoRefDiffPreview = function() {
292 var loadRepoRefDiffPreview = function() {
292
293
293 var url_data = {
294 var url_data = {
294 'repo_name': targetRepo(),
295 'repo_name': targetRepo(),
295 'target_repo': sourceRepo(),
296 'target_repo': sourceRepo(),
296 'source_ref': targetRef()[2],
297 'source_ref': targetRef()[2],
297 'source_ref_type': 'rev',
298 'source_ref_type': 'rev',
298 'target_ref': sourceRef()[2],
299 'target_ref': sourceRef()[2],
299 'target_ref_type': 'rev',
300 'target_ref_type': 'rev',
300 'merge': true,
301 'merge': true,
301 '_': Date.now() // bypass browser caching
302 '_': Date.now() // bypass browser caching
302 }; // gather the source/target ref and repo here
303 }; // gather the source/target ref and repo here
303
304
304 if (sourceRef().length !== 3 || targetRef().length !== 3) {
305 if (sourceRef().length !== 3 || targetRef().length !== 3) {
305 prButtonLock(true, "${_('Please select source and target')}");
306 prButtonLock(true, "${_('Please select source and target')}");
306 return;
307 return;
307 }
308 }
308 var url = pyroutes.url('repo_compare', url_data);
309 var url = pyroutes.url('repo_compare', url_data);
309
310
310 // lock PR button, so we cannot send PR before it's calculated
311 // lock PR button, so we cannot send PR before it's calculated
311 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
312 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
312
313
313 if (loadRepoRefDiffPreview._currentRequest) {
314 if (loadRepoRefDiffPreview._currentRequest) {
314 loadRepoRefDiffPreview._currentRequest.abort();
315 loadRepoRefDiffPreview._currentRequest.abort();
315 }
316 }
316
317
317 loadRepoRefDiffPreview._currentRequest = $.get(url)
318 loadRepoRefDiffPreview._currentRequest = $.get(url)
318 .error(function(data, textStatus, errorThrown) {
319 .error(function(data, textStatus, errorThrown) {
319 if (textStatus !== 'abort') {
320 if (textStatus !== 'abort') {
320 alert(
321 alert(
321 "Error while processing request.\nError code {0} ({1}).".format(
322 "Error while processing request.\nError code {0} ({1}).".format(
322 data.status, data.statusText));
323 data.status, data.statusText));
323 }
324 }
324
325
325 })
326 })
326 .done(function(data) {
327 .done(function(data) {
327 loadRepoRefDiffPreview._currentRequest = null;
328 loadRepoRefDiffPreview._currentRequest = null;
328 $('#pull_request_overview').html(data);
329 $('#pull_request_overview').html(data);
329
330
330 var commitElements = $(data).find('tr[commit_id]');
331 var commitElements = $(data).find('tr[commit_id]');
331
332
332 var prTitleAndDesc = getTitleAndDescription(
333 var prTitleAndDesc = getTitleAndDescription(
333 sourceRef()[1], commitElements, 5);
334 sourceRef()[1], commitElements, 5);
334
335
335 var title = prTitleAndDesc[0];
336 var title = prTitleAndDesc[0];
336 var proposedDescription = prTitleAndDesc[1];
337 var proposedDescription = prTitleAndDesc[1];
337
338
338 var useGeneratedTitle = (
339 var useGeneratedTitle = (
339 $('#pullrequest_title').hasClass('autogenerated-title') ||
340 $('#pullrequest_title').hasClass('autogenerated-title') ||
340 $('#pullrequest_title').val() === "");
341 $('#pullrequest_title').val() === "");
341
342
342 if (title && useGeneratedTitle) {
343 if (title && useGeneratedTitle) {
343 // use generated title if we haven't specified our own
344 // use generated title if we haven't specified our own
344 $('#pullrequest_title').val(title);
345 $('#pullrequest_title').val(title);
345 $('#pullrequest_title').addClass('autogenerated-title');
346 $('#pullrequest_title').addClass('autogenerated-title');
346
347
347 }
348 }
348
349
349 var useGeneratedDescription = (
350 var useGeneratedDescription = (
350 !codeMirrorInstance._userDefinedDesc ||
351 !codeMirrorInstance._userDefinedDesc ||
351 codeMirrorInstance.getValue() === "");
352 codeMirrorInstance.getValue() === "");
352
353
353 if (proposedDescription && useGeneratedDescription) {
354 if (proposedDescription && useGeneratedDescription) {
354 // set proposed content, if we haven't defined our own,
355 // set proposed content, if we haven't defined our own,
355 // or we don't have description written
356 // or we don't have description written
356 codeMirrorInstance._userDefinedDesc = false; // reset state
357 codeMirrorInstance._userDefinedDesc = false; // reset state
357 codeMirrorInstance.setValue(proposedDescription);
358 codeMirrorInstance.setValue(proposedDescription);
358 }
359 }
359
360
360 var msg = '';
361 var msg = '';
361 if (commitElements.length === 1) {
362 if (commitElements.length === 1) {
362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
363 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
363 } else {
364 } else {
364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
365 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
365 }
366 }
366
367
367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
368 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
368
369
369 if (commitElements.length) {
370 if (commitElements.length) {
370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
371 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
372 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
372 }
373 }
373 else {
374 else {
374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
375 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
375 }
376 }
376
377
377
378
378 });
379 });
379 };
380 };
380
381
381 var Select2Box = function(element, overrides) {
382 var Select2Box = function(element, overrides) {
382 var globalDefaults = {
383 var globalDefaults = {
383 dropdownAutoWidth: true,
384 dropdownAutoWidth: true,
384 containerCssClass: "drop-menu",
385 containerCssClass: "drop-menu",
385 dropdownCssClass: "drop-menu-dropdown"
386 dropdownCssClass: "drop-menu-dropdown"
386 };
387 };
387
388
388 var initSelect2 = function(defaultOptions) {
389 var initSelect2 = function(defaultOptions) {
389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
390 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
390 element.select2(options);
391 element.select2(options);
391 };
392 };
392
393
393 return {
394 return {
394 initRef: function() {
395 initRef: function() {
395 var defaultOptions = {
396 var defaultOptions = {
396 minimumResultsForSearch: 5,
397 minimumResultsForSearch: 5,
397 formatSelection: formatRefSelection
398 formatSelection: formatRefSelection
398 };
399 };
399
400
400 initSelect2(defaultOptions);
401 initSelect2(defaultOptions);
401 },
402 },
402
403
403 initRepo: function(defaultValue, readOnly) {
404 initRepo: function(defaultValue, readOnly) {
404 var defaultOptions = {
405 var defaultOptions = {
405 initSelection : function (element, callback) {
406 initSelection : function (element, callback) {
406 var data = {id: defaultValue, text: defaultValue};
407 var data = {id: defaultValue, text: defaultValue};
407 callback(data);
408 callback(data);
408 }
409 }
409 };
410 };
410
411
411 initSelect2(defaultOptions);
412 initSelect2(defaultOptions);
412
413
413 element.select2('val', defaultSourceRepo);
414 element.select2('val', defaultSourceRepo);
414 if (readOnly === true) {
415 if (readOnly === true) {
415 element.select2('readonly', true);
416 element.select2('readonly', true);
416 }
417 }
417 }
418 }
418 };
419 };
419 };
420 };
420
421
421 var initTargetRefs = function(refsData, selectedRef) {
422 var initTargetRefs = function(refsData, selectedRef) {
422
423
423 Select2Box($targetRef, {
424 Select2Box($targetRef, {
424 placeholder: "${_('Select commit reference')}",
425 placeholder: "${_('Select commit reference')}",
425 query: function(query) {
426 query: function(query) {
426 queryTargetRefs(refsData, query);
427 queryTargetRefs(refsData, query);
427 },
428 },
428 initSelection : initRefSelection(selectedRef)
429 initSelection : initRefSelection(selectedRef)
429 }).initRef();
430 }).initRef();
430
431
431 if (!(selectedRef === undefined)) {
432 if (!(selectedRef === undefined)) {
432 $targetRef.select2('val', selectedRef);
433 $targetRef.select2('val', selectedRef);
433 }
434 }
434 };
435 };
435
436
436 var targetRepoChanged = function(repoData) {
437 var targetRepoChanged = function(repoData) {
437 // generate new DESC of target repo displayed next to select
438 // generate new DESC of target repo displayed next to select
438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
439 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
439 $('#target_repo_desc').html(
440 $('#target_repo_desc').html(
440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
441 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
441 );
442 );
442
443
443 // generate dynamic select2 for refs.
444 // generate dynamic select2 for refs.
444 initTargetRefs(repoData['refs']['select2_refs'],
445 initTargetRefs(repoData['refs']['select2_refs'],
445 repoData['refs']['selected_ref']);
446 repoData['refs']['selected_ref']);
446
447
447 };
448 };
448
449
449 var sourceRefSelect2 = Select2Box($sourceRef, {
450 var sourceRefSelect2 = Select2Box($sourceRef, {
450 placeholder: "${_('Select commit reference')}",
451 placeholder: "${_('Select commit reference')}",
451 query: function(query) {
452 query: function(query) {
452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
453 var initialData = defaultSourceRepoData['refs']['select2_refs'];
453 queryTargetRefs(initialData, query)
454 queryTargetRefs(initialData, query)
454 },
455 },
455 initSelection: initRefSelection()
456 initSelection: initRefSelection()
456 }
457 }
457 );
458 );
458
459
459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
460 var sourceRepoSelect2 = Select2Box($sourceRepo, {
460 query: function(query) {}
461 query: function(query) {}
461 });
462 });
462
463
463 var targetRepoSelect2 = Select2Box($targetRepo, {
464 var targetRepoSelect2 = Select2Box($targetRepo, {
464 cachedDataSource: {},
465 cachedDataSource: {},
465 query: $.debounce(250, function(query) {
466 query: $.debounce(250, function(query) {
466 queryTargetRepo(this, query);
467 queryTargetRepo(this, query);
467 }),
468 }),
468 formatResult: formatRepoResult
469 formatResult: formatRepoResult
469 });
470 });
470
471
471 sourceRefSelect2.initRef();
472 sourceRefSelect2.initRef();
472
473
473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
474 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
474
475
475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
476 targetRepoSelect2.initRepo(defaultTargetRepo, false);
476
477
477 $sourceRef.on('change', function(e){
478 $sourceRef.on('change', function(e){
478 loadRepoRefDiffPreview();
479 loadRepoRefDiffPreview();
479 reviewersController.loadDefaultReviewers(
480 reviewersController.loadDefaultReviewers(
480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
481 sourceRepo(), sourceRef(), targetRepo(), targetRef());
481 });
482 });
482
483
483 $targetRef.on('change', function(e){
484 $targetRef.on('change', function(e){
484 loadRepoRefDiffPreview();
485 loadRepoRefDiffPreview();
485 reviewersController.loadDefaultReviewers(
486 reviewersController.loadDefaultReviewers(
486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
487 sourceRepo(), sourceRef(), targetRepo(), targetRef());
487 });
488 });
488
489
489 $targetRepo.on('change', function(e){
490 $targetRepo.on('change', function(e){
490 var repoName = $(this).val();
491 var repoName = $(this).val();
491 calculateContainerWidth();
492 calculateContainerWidth();
492 $targetRef.select2('destroy');
493 $targetRef.select2('destroy');
493 $('#target_ref_loading').show();
494 $('#target_ref_loading').show();
494
495
495 $.ajax({
496 $.ajax({
496 url: pyroutes.url('pullrequest_repo_refs',
497 url: pyroutes.url('pullrequest_repo_refs',
497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
498 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
498 data: {},
499 data: {},
499 dataType: 'json',
500 dataType: 'json',
500 type: 'GET',
501 type: 'GET',
501 success: function(data) {
502 success: function(data) {
502 $('#target_ref_loading').hide();
503 $('#target_ref_loading').hide();
503 targetRepoChanged(data);
504 targetRepoChanged(data);
504 loadRepoRefDiffPreview();
505 loadRepoRefDiffPreview();
505 },
506 },
506 error: function(data, textStatus, errorThrown) {
507 error: function(data, textStatus, errorThrown) {
507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
508 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
508 }
509 }
509 })
510 })
510
511
511 });
512 });
512
513
514 $pullRequestForm.on('submit', function(e){
515 prButtonLock(true, null, 'all');
516 });
517
513 prButtonLock(true, "${_('Please select source and target')}", 'all');
518 prButtonLock(true, "${_('Please select source and target')}", 'all');
514
519
515 // auto-load on init, the target refs select2
520 // auto-load on init, the target refs select2
516 calculateContainerWidth();
521 calculateContainerWidth();
517 targetRepoChanged(defaultTargetRepoData);
522 targetRepoChanged(defaultTargetRepoData);
518
523
519 $('#pullrequest_title').on('keyup', function(e){
524 $('#pullrequest_title').on('keyup', function(e){
520 $(this).removeClass('autogenerated-title');
525 $(this).removeClass('autogenerated-title');
521 });
526 });
522
527
523 % if c.default_source_ref:
528 % if c.default_source_ref:
524 // in case we have a pre-selected value, use it now
529 // in case we have a pre-selected value, use it now
525 $sourceRef.select2('val', '${c.default_source_ref}');
530 $sourceRef.select2('val', '${c.default_source_ref}');
526 // diff preview load
531 // diff preview load
527 loadRepoRefDiffPreview();
532 loadRepoRefDiffPreview();
528 // default reviewers
533 // default reviewers
529 reviewersController.loadDefaultReviewers(
534 reviewersController.loadDefaultReviewers(
530 sourceRepo(), sourceRef(), targetRepo(), targetRef());
535 sourceRepo(), sourceRef(), targetRepo(), targetRef());
531 % endif
536 % endif
532
537
533 ReviewerAutoComplete('#user');
538 ReviewerAutoComplete('#user');
534 });
539 });
535 </script>
540 </script>
536
541
537 </%def>
542 </%def>
General Comments 0
You need to be logged in to leave comments. Login now