##// END OF EJS Templates
default-reviewers: handle no common ancestor case.
marcink -
r4520:10c5ceba stable
parent child Browse files
Show More
@@ -1,82 +1,89 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.apps._base import RepoAppView
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
26 from rhodecode.apps.repository.utils import get_default_reviewers_data
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
28 from rhodecode.lib.vcs.backends.base import Reference
28 from rhodecode.lib.vcs.backends.base import Reference
29 from rhodecode.model.db import Repository
29 from rhodecode.model.db import Repository
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class RepoReviewRulesView(RepoAppView):
34 class RepoReviewRulesView(RepoAppView):
35 def load_default_context(self):
35 def load_default_context(self):
36 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
37 return c
37 return c
38
38
39 @LoginRequired()
39 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.admin')
40 @HasRepoPermissionAnyDecorator('repository.admin')
41 @view_config(
41 @view_config(
42 route_name='repo_reviewers', request_method='GET',
42 route_name='repo_reviewers', request_method='GET',
43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
44 def repo_review_rules(self):
44 def repo_review_rules(self):
45 c = self.load_default_context()
45 c = self.load_default_context()
46 c.active = 'reviewers'
46 c.active = 'reviewers'
47
47
48 return self._get_template_context(c)
48 return self._get_template_context(c)
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @HasRepoPermissionAnyDecorator(
51 @HasRepoPermissionAnyDecorator(
52 'repository.read', 'repository.write', 'repository.admin')
52 'repository.read', 'repository.write', 'repository.admin')
53 @view_config(
53 @view_config(
54 route_name='repo_default_reviewers_data', request_method='GET',
54 route_name='repo_default_reviewers_data', request_method='GET',
55 renderer='json_ext')
55 renderer='json_ext')
56 def repo_default_reviewers_data(self):
56 def repo_default_reviewers_data(self):
57 self.load_default_context()
57 self.load_default_context()
58
58
59 request = self.request
59 request = self.request
60 source_repo = self.db_repo
60 source_repo = self.db_repo
61 source_repo_name = source_repo.repo_name
61 source_repo_name = source_repo.repo_name
62 target_repo_name = request.GET.get('target_repo', source_repo_name)
62 target_repo_name = request.GET.get('target_repo', source_repo_name)
63 target_repo = Repository.get_by_repo_name(target_repo_name)
63 target_repo = Repository.get_by_repo_name(target_repo_name)
64
64
65 current_user = request.user.get_instance()
65 current_user = request.user.get_instance()
66
66
67 source_commit_id = request.GET['source_ref']
67 source_commit_id = request.GET['source_ref']
68 source_type = request.GET['source_ref_type']
68 source_type = request.GET['source_ref_type']
69 source_name = request.GET['source_ref_name']
69 source_name = request.GET['source_ref_name']
70
70
71 target_commit_id = request.GET['target_ref']
71 target_commit_id = request.GET['target_ref']
72 target_type = request.GET['target_ref_type']
72 target_type = request.GET['target_ref_type']
73 target_name = request.GET['target_ref_name']
73 target_name = request.GET['target_ref_name']
74
74
75 try:
75 review_data = get_default_reviewers_data(
76 review_data = get_default_reviewers_data(
76 current_user,
77 current_user,
77 source_repo,
78 source_repo,
78 Reference(source_type, source_name, source_commit_id),
79 Reference(source_type, source_name, source_commit_id),
79 target_repo,
80 target_repo,
80 Reference(target_type, target_name, target_commit_id)
81 Reference(target_type, target_name, target_commit_id)
81 )
82 )
83 except ValueError:
84 # No common ancestor
85 msg = "No Common ancestor found between target and source reference"
86 log.exception(msg)
87 return {'diff_info': {'error': msg}}
88
82 return review_data
89 return review_data
@@ -1,1188 +1,1191 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#pr_submit').attr('disabled', 'disabled');
44 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#pr_submit').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = value['message'];
78 var rawMessage = value['message'];
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 var rawMessage = elements[0]['message'];
83 var rawMessage = elements[0]['message'];
84 title = rawMessage.split('\n')[0];
84 title = rawMessage.split('\n')[0];
85 }
85 }
86 else {
86 else {
87 // use reference name
87 // use reference name
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
89 var refType = sourceRefType;
89 var refType = sourceRefType;
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
91 }
91 }
92
92
93 return [title, desc]
93 return [title, desc]
94 };
94 };
95
95
96
96
97 window.ReviewersController = function () {
97 window.ReviewersController = function () {
98 var self = this;
98 var self = this;
99 this.$loadingIndicator = $('.calculate-reviewers');
99 this.$loadingIndicator = $('.calculate-reviewers');
100 this.$reviewRulesContainer = $('#review_rules');
100 this.$reviewRulesContainer = $('#review_rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
102 this.$userRule = $('.pr-user-rule-container');
102 this.$userRule = $('.pr-user-rule-container');
103 this.$reviewMembers = $('#review_members');
103 this.$reviewMembers = $('#review_members');
104 this.$observerMembers = $('#observer_members');
104 this.$observerMembers = $('#observer_members');
105
105
106 this.currentRequest = null;
106 this.currentRequest = null;
107 this.diffData = null;
107 this.diffData = null;
108 this.enabledRules = [];
108 this.enabledRules = [];
109 // sync with db.py entries
109 // sync with db.py entries
110 this.ROLE_REVIEWER = 'reviewer';
110 this.ROLE_REVIEWER = 'reviewer';
111 this.ROLE_OBSERVER = 'observer'
111 this.ROLE_OBSERVER = 'observer'
112
112
113 //dummy handler, we might register our own later
113 //dummy handler, we might register our own later
114 this.diffDataHandler = function (data) {};
114 this.diffDataHandler = function (data) {};
115
115
116 this.defaultForbidUsers = function () {
116 this.defaultForbidUsers = function () {
117 return [
117 return [
118 {
118 {
119 'username': 'default',
119 'username': 'default',
120 'user_id': templateContext.default_user.user_id
120 'user_id': templateContext.default_user.user_id
121 }
121 }
122 ];
122 ];
123 };
123 };
124
124
125 // init default forbidden users
125 // init default forbidden users
126 this.forbidUsers = this.defaultForbidUsers();
126 this.forbidUsers = this.defaultForbidUsers();
127
127
128 this.hideReviewRules = function () {
128 this.hideReviewRules = function () {
129 self.$reviewRulesContainer.hide();
129 self.$reviewRulesContainer.hide();
130 $(self.$userRule.selector).hide();
130 $(self.$userRule.selector).hide();
131 };
131 };
132
132
133 this.showReviewRules = function () {
133 this.showReviewRules = function () {
134 self.$reviewRulesContainer.show();
134 self.$reviewRulesContainer.show();
135 $(self.$userRule.selector).show();
135 $(self.$userRule.selector).show();
136 };
136 };
137
137
138 this.addRule = function (ruleText) {
138 this.addRule = function (ruleText) {
139 self.showReviewRules();
139 self.showReviewRules();
140 self.enabledRules.push(ruleText);
140 self.enabledRules.push(ruleText);
141 return '<div>- {0}</div>'.format(ruleText)
141 return '<div>- {0}</div>'.format(ruleText)
142 };
142 };
143
143
144 this.increaseCounter = function(role) {
144 this.increaseCounter = function(role) {
145 if (role === self.ROLE_REVIEWER) {
145 if (role === self.ROLE_REVIEWER) {
146 var $elem = $('#reviewers-cnt')
146 var $elem = $('#reviewers-cnt')
147 var cnt = parseInt($elem.data('count') || 0)
147 var cnt = parseInt($elem.data('count') || 0)
148 cnt +=1
148 cnt +=1
149 $elem.html(cnt);
149 $elem.html(cnt);
150 $elem.data('count', cnt);
150 $elem.data('count', cnt);
151 }
151 }
152 else if (role === self.ROLE_OBSERVER) {
152 else if (role === self.ROLE_OBSERVER) {
153 var $elem = $('#observers-cnt');
153 var $elem = $('#observers-cnt');
154 var cnt = parseInt($elem.data('count') || 0)
154 var cnt = parseInt($elem.data('count') || 0)
155 cnt +=1
155 cnt +=1
156 $elem.html(cnt);
156 $elem.html(cnt);
157 $elem.data('count', cnt);
157 $elem.data('count', cnt);
158 }
158 }
159 }
159 }
160
160
161 this.resetCounter = function () {
161 this.resetCounter = function () {
162 var $elem = $('#reviewers-cnt');
162 var $elem = $('#reviewers-cnt');
163
163
164 $elem.data('count', 0);
164 $elem.data('count', 0);
165 $elem.html(0);
165 $elem.html(0);
166
166
167 var $elem = $('#observers-cnt');
167 var $elem = $('#observers-cnt');
168
168
169 $elem.data('count', 0);
169 $elem.data('count', 0);
170 $elem.html(0);
170 $elem.html(0);
171 }
171 }
172
172
173 this.loadReviewRules = function (data) {
173 this.loadReviewRules = function (data) {
174 self.diffData = data;
174 self.diffData = data;
175
175
176 // reset forbidden Users
176 // reset forbidden Users
177 this.forbidUsers = self.defaultForbidUsers();
177 this.forbidUsers = self.defaultForbidUsers();
178
178
179 // reset state of review rules
179 // reset state of review rules
180 self.$rulesList.html('');
180 self.$rulesList.html('');
181
181
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
183 // default rule, case for older repo that don't have any rules stored
183 // default rule, case for older repo that don't have any rules stored
184 self.$rulesList.append(
184 self.$rulesList.append(
185 self.addRule(
185 self.addRule(
186 _gettext('All reviewers must vote.'))
186 _gettext('All reviewers must vote.'))
187 );
187 );
188 return self.forbidUsers
188 return self.forbidUsers
189 }
189 }
190
190
191 if (data.rules.voting !== undefined) {
191 if (data.rules.voting !== undefined) {
192 if (data.rules.voting < 0) {
192 if (data.rules.voting < 0) {
193 self.$rulesList.append(
193 self.$rulesList.append(
194 self.addRule(
194 self.addRule(
195 _gettext('All individual reviewers must vote.'))
195 _gettext('All individual reviewers must vote.'))
196 )
196 )
197 } else if (data.rules.voting === 1) {
197 } else if (data.rules.voting === 1) {
198 self.$rulesList.append(
198 self.$rulesList.append(
199 self.addRule(
199 self.addRule(
200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
201 )
201 )
202
202
203 } else {
203 } else {
204 self.$rulesList.append(
204 self.$rulesList.append(
205 self.addRule(
205 self.addRule(
206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
207 )
207 )
208 }
208 }
209 }
209 }
210
210
211 if (data.rules.voting_groups !== undefined) {
211 if (data.rules.voting_groups !== undefined) {
212 $.each(data.rules.voting_groups, function (index, rule_data) {
212 $.each(data.rules.voting_groups, function (index, rule_data) {
213 self.$rulesList.append(
213 self.$rulesList.append(
214 self.addRule(rule_data.text)
214 self.addRule(rule_data.text)
215 )
215 )
216 });
216 });
217 }
217 }
218
218
219 if (data.rules.use_code_authors_for_review) {
219 if (data.rules.use_code_authors_for_review) {
220 self.$rulesList.append(
220 self.$rulesList.append(
221 self.addRule(
221 self.addRule(
222 _gettext('Reviewers picked from source code changes.'))
222 _gettext('Reviewers picked from source code changes.'))
223 )
223 )
224 }
224 }
225
225
226 if (data.rules.forbid_adding_reviewers) {
226 if (data.rules.forbid_adding_reviewers) {
227 $('#add_reviewer_input').remove();
227 $('#add_reviewer_input').remove();
228 self.$rulesList.append(
228 self.$rulesList.append(
229 self.addRule(
229 self.addRule(
230 _gettext('Adding new reviewers is forbidden.'))
230 _gettext('Adding new reviewers is forbidden.'))
231 )
231 )
232 }
232 }
233
233
234 if (data.rules.forbid_author_to_review) {
234 if (data.rules.forbid_author_to_review) {
235 self.forbidUsers.push(data.rules_data.pr_author);
235 self.forbidUsers.push(data.rules_data.pr_author);
236 self.$rulesList.append(
236 self.$rulesList.append(
237 self.addRule(
237 self.addRule(
238 _gettext('Author is not allowed to be a reviewer.'))
238 _gettext('Author is not allowed to be a reviewer.'))
239 )
239 )
240 }
240 }
241
241
242 if (data.rules.forbid_commit_author_to_review) {
242 if (data.rules.forbid_commit_author_to_review) {
243
243
244 if (data.rules_data.forbidden_users) {
244 if (data.rules_data.forbidden_users) {
245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
246 self.forbidUsers.push(member_data)
246 self.forbidUsers.push(member_data)
247 });
247 });
248 }
248 }
249
249
250 self.$rulesList.append(
250 self.$rulesList.append(
251 self.addRule(
251 self.addRule(
252 _gettext('Commit Authors are not allowed to be a reviewer.'))
252 _gettext('Commit Authors are not allowed to be a reviewer.'))
253 )
253 )
254 }
254 }
255
255
256 // we don't have any rules set, so we inform users about it
256 // we don't have any rules set, so we inform users about it
257 if (self.enabledRules.length === 0) {
257 if (self.enabledRules.length === 0) {
258 self.addRule(
258 self.addRule(
259 _gettext('No review rules set.'))
259 _gettext('No review rules set.'))
260 }
260 }
261
261
262 return self.forbidUsers
262 return self.forbidUsers
263 };
263 };
264
264
265 this.emptyTables = function () {
265 this.emptyTables = function () {
266 self.emptyReviewersTable();
266 self.emptyReviewersTable();
267 self.emptyObserversTable();
267 self.emptyObserversTable();
268
268
269 // Also reset counters.
269 // Also reset counters.
270 self.resetCounter();
270 self.resetCounter();
271 }
271 }
272
272
273 this.emptyReviewersTable = function (withText) {
273 this.emptyReviewersTable = function (withText) {
274 self.$reviewMembers.empty();
274 self.$reviewMembers.empty();
275 if (withText !== undefined) {
275 if (withText !== undefined) {
276 self.$reviewMembers.html(withText)
276 self.$reviewMembers.html(withText)
277 }
277 }
278 };
278 };
279
279
280 this.emptyObserversTable = function (withText) {
280 this.emptyObserversTable = function (withText) {
281 self.$observerMembers.empty();
281 self.$observerMembers.empty();
282 if (withText !== undefined) {
282 if (withText !== undefined) {
283 self.$observerMembers.html(withText)
283 self.$observerMembers.html(withText)
284 }
284 }
285 }
285 }
286
286
287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
288
288
289 if (self.currentRequest) {
289 if (self.currentRequest) {
290 // make sure we cleanup old running requests before triggering this again
290 // make sure we cleanup old running requests before triggering this again
291 self.currentRequest.abort();
291 self.currentRequest.abort();
292 }
292 }
293
293
294 self.$loadingIndicator.show();
294 self.$loadingIndicator.show();
295
295
296 // reset reviewer/observe members
296 // reset reviewer/observe members
297 self.emptyTables();
297 self.emptyTables();
298
298
299 prButtonLock(true, null, 'reviewers');
299 prButtonLock(true, null, 'reviewers');
300 $('#user').hide(); // hide user autocomplete before load
300 $('#user').hide(); // hide user autocomplete before load
301 $('#observer').hide(); //hide observer autocomplete before load
301 $('#observer').hide(); //hide observer autocomplete before load
302
302
303 // lock PR button, so we cannot send PR before it's calculated
303 // lock PR button, so we cannot send PR before it's calculated
304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
305
305
306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
307 // don't load defaults in case we're missing some refs...
307 // don't load defaults in case we're missing some refs...
308 self.$loadingIndicator.hide();
308 self.$loadingIndicator.hide();
309 return
309 return
310 }
310 }
311
311
312 var url = pyroutes.url('repo_default_reviewers_data',
312 var url = pyroutes.url('repo_default_reviewers_data',
313 {
313 {
314 'repo_name': templateContext.repo_name,
314 'repo_name': templateContext.repo_name,
315 'source_repo': sourceRepo,
315 'source_repo': sourceRepo,
316 'source_ref_type': sourceRef[0],
316 'source_ref_type': sourceRef[0],
317 'source_ref_name': sourceRef[1],
317 'source_ref_name': sourceRef[1],
318 'source_ref': sourceRef[2],
318 'source_ref': sourceRef[2],
319 'target_repo': targetRepo,
319 'target_repo': targetRepo,
320 'target_ref': targetRef[2],
320 'target_ref': targetRef[2],
321 'target_ref_type': sourceRef[0],
321 'target_ref_type': sourceRef[0],
322 'target_ref_name': sourceRef[1]
322 'target_ref_name': sourceRef[1]
323 });
323 });
324
324
325 self.currentRequest = $.ajax({
325 self.currentRequest = $.ajax({
326 url: url,
326 url: url,
327 headers: {'X-PARTIAL-XHR': true},
327 headers: {'X-PARTIAL-XHR': true},
328 type: 'GET',
328 type: 'GET',
329 success: function (data) {
329 success: function (data) {
330
330
331 self.currentRequest = null;
331 self.currentRequest = null;
332
332
333 // review rules
333 // review rules
334 self.loadReviewRules(data);
334 self.loadReviewRules(data);
335 self.handleDiffData(data["diff_info"]);
335 var diffHandled = self.handleDiffData(data["diff_info"]);
336 if (diffHandled === false) {
337 return
338 }
336
339
337 for (var i = 0; i < data.reviewers.length; i++) {
340 for (var i = 0; i < data.reviewers.length; i++) {
338 var reviewer = data.reviewers[i];
341 var reviewer = data.reviewers[i];
339 // load reviewer rules from the repo data
342 // load reviewer rules from the repo data
340 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
343 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
341 }
344 }
342
345
343
346
344 self.$loadingIndicator.hide();
347 self.$loadingIndicator.hide();
345 prButtonLock(false, null, 'reviewers');
348 prButtonLock(false, null, 'reviewers');
346
349
347 $('#user').show(); // show user autocomplete before load
350 $('#user').show(); // show user autocomplete before load
348 $('#observer').show(); // show observer autocomplete before load
351 $('#observer').show(); // show observer autocomplete before load
349
352
350 var commitElements = data["diff_info"]['commits'];
353 var commitElements = data["diff_info"]['commits'];
351
354
352 if (commitElements.length === 0) {
355 if (commitElements.length === 0) {
353 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
356 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
354 _gettext('There are no commits to merge.'));
357 _gettext('There are no commits to merge.'));
355 prButtonLock(true, noCommitsMsg, 'all');
358 prButtonLock(true, noCommitsMsg, 'all');
356
359
357 } else {
360 } else {
358 // un-lock PR button, so we cannot send PR before it's calculated
361 // un-lock PR button, so we cannot send PR before it's calculated
359 prButtonLock(false, null, 'compare');
362 prButtonLock(false, null, 'compare');
360 }
363 }
361
364
362 },
365 },
363 error: function (jqXHR, textStatus, errorThrown) {
366 error: function (jqXHR, textStatus, errorThrown) {
364 var prefix = "Loading diff and reviewers/observers failed\n"
367 var prefix = "Loading diff and reviewers/observers failed\n"
365 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
368 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
366 ajaxErrorSwal(message);
369 ajaxErrorSwal(message);
367 }
370 }
368 });
371 });
369
372
370 };
373 };
371
374
372 // check those, refactor
375 // check those, refactor
373 this.removeMember = function (reviewer_id, mark_delete) {
376 this.removeMember = function (reviewer_id, mark_delete) {
374 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
377 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
375
378
376 if (typeof (mark_delete) === undefined) {
379 if (typeof (mark_delete) === undefined) {
377 mark_delete = false;
380 mark_delete = false;
378 }
381 }
379
382
380 if (mark_delete === true) {
383 if (mark_delete === true) {
381 if (reviewer) {
384 if (reviewer) {
382 // now delete the input
385 // now delete the input
383 $('#reviewer_{0} input'.format(reviewer_id)).remove();
386 $('#reviewer_{0} input'.format(reviewer_id)).remove();
384 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
387 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
385 // mark as to-delete
388 // mark as to-delete
386 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
389 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
387 obj.addClass('to-delete');
390 obj.addClass('to-delete');
388 obj.css({"text-decoration": "line-through", "opacity": 0.5});
391 obj.css({"text-decoration": "line-through", "opacity": 0.5});
389 }
392 }
390 } else {
393 } else {
391 $('#reviewer_{0}'.format(reviewer_id)).remove();
394 $('#reviewer_{0}'.format(reviewer_id)).remove();
392 }
395 }
393 };
396 };
394
397
395 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
398 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
396
399
397 var id = reviewer_obj.user_id;
400 var id = reviewer_obj.user_id;
398 var username = reviewer_obj.username;
401 var username = reviewer_obj.username;
399
402
400 reasons = reasons || [];
403 reasons = reasons || [];
401 mandatory = mandatory || false;
404 mandatory = mandatory || false;
402 role = role || self.ROLE_REVIEWER
405 role = role || self.ROLE_REVIEWER
403
406
404 // register current set IDS to check if we don't have this ID already in
407 // register current set IDS to check if we don't have this ID already in
405 // and prevent duplicates
408 // and prevent duplicates
406 var currentIds = [];
409 var currentIds = [];
407
410
408 $.each($('.reviewer_entry'), function (index, value) {
411 $.each($('.reviewer_entry'), function (index, value) {
409 currentIds.push($(value).data('reviewerUserId'))
412 currentIds.push($(value).data('reviewerUserId'))
410 })
413 })
411
414
412 var userAllowedReview = function (userId) {
415 var userAllowedReview = function (userId) {
413 var allowed = true;
416 var allowed = true;
414 $.each(self.forbidUsers, function (index, member_data) {
417 $.each(self.forbidUsers, function (index, member_data) {
415 if (parseInt(userId) === member_data['user_id']) {
418 if (parseInt(userId) === member_data['user_id']) {
416 allowed = false;
419 allowed = false;
417 return false // breaks the loop
420 return false // breaks the loop
418 }
421 }
419 });
422 });
420 return allowed
423 return allowed
421 };
424 };
422
425
423 var userAllowed = userAllowedReview(id);
426 var userAllowed = userAllowedReview(id);
424
427
425 if (!userAllowed) {
428 if (!userAllowed) {
426 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
429 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
427 } else {
430 } else {
428 // only add if it's not there
431 // only add if it's not there
429 var alreadyReviewer = currentIds.indexOf(id) != -1;
432 var alreadyReviewer = currentIds.indexOf(id) != -1;
430
433
431 if (alreadyReviewer) {
434 if (alreadyReviewer) {
432 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
435 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
433 } else {
436 } else {
434
437
435 var reviewerEntry = renderTemplate('reviewMemberEntry', {
438 var reviewerEntry = renderTemplate('reviewMemberEntry', {
436 'member': reviewer_obj,
439 'member': reviewer_obj,
437 'mandatory': mandatory,
440 'mandatory': mandatory,
438 'role': role,
441 'role': role,
439 'reasons': reasons,
442 'reasons': reasons,
440 'allowed_to_update': true,
443 'allowed_to_update': true,
441 'review_status': 'not_reviewed',
444 'review_status': 'not_reviewed',
442 'review_status_label': _gettext('Not Reviewed'),
445 'review_status_label': _gettext('Not Reviewed'),
443 'user_group': reviewer_obj.user_group,
446 'user_group': reviewer_obj.user_group,
444 'create': true,
447 'create': true,
445 'rule_show': true,
448 'rule_show': true,
446 })
449 })
447
450
448 if (role === self.ROLE_REVIEWER) {
451 if (role === self.ROLE_REVIEWER) {
449 $(self.$reviewMembers.selector).append(reviewerEntry);
452 $(self.$reviewMembers.selector).append(reviewerEntry);
450 self.increaseCounter(self.ROLE_REVIEWER);
453 self.increaseCounter(self.ROLE_REVIEWER);
451 $('#reviewer-empty-msg').remove()
454 $('#reviewer-empty-msg').remove()
452 }
455 }
453 else if (role === self.ROLE_OBSERVER) {
456 else if (role === self.ROLE_OBSERVER) {
454 $(self.$observerMembers.selector).append(reviewerEntry);
457 $(self.$observerMembers.selector).append(reviewerEntry);
455 self.increaseCounter(self.ROLE_OBSERVER);
458 self.increaseCounter(self.ROLE_OBSERVER);
456 $('#observer-empty-msg').remove();
459 $('#observer-empty-msg').remove();
457 }
460 }
458
461
459 tooltipActivate();
462 tooltipActivate();
460 }
463 }
461 }
464 }
462
465
463 };
466 };
464
467
465 this.updateReviewers = function (repo_name, pull_request_id, role) {
468 this.updateReviewers = function (repo_name, pull_request_id, role) {
466 if (role === 'reviewer') {
469 if (role === 'reviewer') {
467 var postData = $('#reviewers input').serialize();
470 var postData = $('#reviewers input').serialize();
468 _updatePullRequest(repo_name, pull_request_id, postData);
471 _updatePullRequest(repo_name, pull_request_id, postData);
469 } else if (role === 'observer') {
472 } else if (role === 'observer') {
470 var postData = $('#observers input').serialize();
473 var postData = $('#observers input').serialize();
471 _updatePullRequest(repo_name, pull_request_id, postData);
474 _updatePullRequest(repo_name, pull_request_id, postData);
472 }
475 }
473 };
476 };
474
477
475 this.handleDiffData = function (data) {
478 this.handleDiffData = function (data) {
476 self.diffDataHandler(data)
479 return self.diffDataHandler(data)
477 }
480 }
478 };
481 };
479
482
480
483
481 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
484 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
482 var url = pyroutes.url(
485 var url = pyroutes.url(
483 'pullrequest_update',
486 'pullrequest_update',
484 {"repo_name": repo_name, "pull_request_id": pull_request_id});
487 {"repo_name": repo_name, "pull_request_id": pull_request_id});
485 if (typeof postData === 'string' ) {
488 if (typeof postData === 'string' ) {
486 postData += '&csrf_token=' + CSRF_TOKEN;
489 postData += '&csrf_token=' + CSRF_TOKEN;
487 } else {
490 } else {
488 postData.csrf_token = CSRF_TOKEN;
491 postData.csrf_token = CSRF_TOKEN;
489 }
492 }
490
493
491 var success = function(o) {
494 var success = function(o) {
492 var redirectUrl = o['redirect_url'];
495 var redirectUrl = o['redirect_url'];
493 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
496 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
494 window.location = redirectUrl;
497 window.location = redirectUrl;
495 } else {
498 } else {
496 window.location.reload();
499 window.location.reload();
497 }
500 }
498 };
501 };
499
502
500 ajaxPOST(url, postData, success);
503 ajaxPOST(url, postData, success);
501 };
504 };
502
505
503 /**
506 /**
504 * PULL REQUEST update commits
507 * PULL REQUEST update commits
505 */
508 */
506 var updateCommits = function(repo_name, pull_request_id, force) {
509 var updateCommits = function(repo_name, pull_request_id, force) {
507 var postData = {
510 var postData = {
508 'update_commits': true
511 'update_commits': true
509 };
512 };
510 if (force !== undefined && force === true) {
513 if (force !== undefined && force === true) {
511 postData['force_refresh'] = true
514 postData['force_refresh'] = true
512 }
515 }
513 _updatePullRequest(repo_name, pull_request_id, postData);
516 _updatePullRequest(repo_name, pull_request_id, postData);
514 };
517 };
515
518
516
519
517 /**
520 /**
518 * PULL REQUEST edit info
521 * PULL REQUEST edit info
519 */
522 */
520 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
523 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
521 var url = pyroutes.url(
524 var url = pyroutes.url(
522 'pullrequest_update',
525 'pullrequest_update',
523 {"repo_name": repo_name, "pull_request_id": pull_request_id});
526 {"repo_name": repo_name, "pull_request_id": pull_request_id});
524
527
525 var postData = {
528 var postData = {
526 'title': title,
529 'title': title,
527 'description': description,
530 'description': description,
528 'description_renderer': renderer,
531 'description_renderer': renderer,
529 'edit_pull_request': true,
532 'edit_pull_request': true,
530 'csrf_token': CSRF_TOKEN
533 'csrf_token': CSRF_TOKEN
531 };
534 };
532 var success = function(o) {
535 var success = function(o) {
533 window.location.reload();
536 window.location.reload();
534 };
537 };
535 ajaxPOST(url, postData, success);
538 ajaxPOST(url, postData, success);
536 };
539 };
537
540
538
541
539 /**
542 /**
540 * autocomplete handler for reviewers/observers
543 * autocomplete handler for reviewers/observers
541 */
544 */
542 var autoCompleteHandler = function (inputId, controller, role) {
545 var autoCompleteHandler = function (inputId, controller, role) {
543
546
544 return function (element, data) {
547 return function (element, data) {
545 var mandatory = false;
548 var mandatory = false;
546 var reasons = [_gettext('added manually by "{0}"').format(
549 var reasons = [_gettext('added manually by "{0}"').format(
547 templateContext.rhodecode_user.username)];
550 templateContext.rhodecode_user.username)];
548
551
549 // add whole user groups
552 // add whole user groups
550 if (data.value_type == 'user_group') {
553 if (data.value_type == 'user_group') {
551 reasons.push(_gettext('member of "{0}"').format(data.value_display));
554 reasons.push(_gettext('member of "{0}"').format(data.value_display));
552
555
553 $.each(data.members, function (index, member_data) {
556 $.each(data.members, function (index, member_data) {
554 var reviewer = member_data;
557 var reviewer = member_data;
555 reviewer['user_id'] = member_data['id'];
558 reviewer['user_id'] = member_data['id'];
556 reviewer['gravatar_link'] = member_data['icon_link'];
559 reviewer['gravatar_link'] = member_data['icon_link'];
557 reviewer['user_link'] = member_data['profile_link'];
560 reviewer['user_link'] = member_data['profile_link'];
558 reviewer['rules'] = [];
561 reviewer['rules'] = [];
559 controller.addMember(reviewer, reasons, mandatory, role);
562 controller.addMember(reviewer, reasons, mandatory, role);
560 })
563 })
561 }
564 }
562 // add single user
565 // add single user
563 else {
566 else {
564 var reviewer = data;
567 var reviewer = data;
565 reviewer['user_id'] = data['id'];
568 reviewer['user_id'] = data['id'];
566 reviewer['gravatar_link'] = data['icon_link'];
569 reviewer['gravatar_link'] = data['icon_link'];
567 reviewer['user_link'] = data['profile_link'];
570 reviewer['user_link'] = data['profile_link'];
568 reviewer['rules'] = [];
571 reviewer['rules'] = [];
569 controller.addMember(reviewer, reasons, mandatory, role);
572 controller.addMember(reviewer, reasons, mandatory, role);
570 }
573 }
571
574
572 $(inputId).val('');
575 $(inputId).val('');
573 }
576 }
574 }
577 }
575
578
576 /**
579 /**
577 * Reviewer autocomplete
580 * Reviewer autocomplete
578 */
581 */
579 var ReviewerAutoComplete = function (inputId, controller) {
582 var ReviewerAutoComplete = function (inputId, controller) {
580 var self = this;
583 var self = this;
581 self.controller = controller;
584 self.controller = controller;
582 self.inputId = inputId;
585 self.inputId = inputId;
583 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
586 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
584
587
585 $(inputId).autocomplete({
588 $(inputId).autocomplete({
586 serviceUrl: pyroutes.url('user_autocomplete_data'),
589 serviceUrl: pyroutes.url('user_autocomplete_data'),
587 minChars: 2,
590 minChars: 2,
588 maxHeight: 400,
591 maxHeight: 400,
589 deferRequestBy: 300, //miliseconds
592 deferRequestBy: 300, //miliseconds
590 showNoSuggestionNotice: true,
593 showNoSuggestionNotice: true,
591 tabDisabled: true,
594 tabDisabled: true,
592 autoSelectFirst: true,
595 autoSelectFirst: true,
593 params: {
596 params: {
594 user_id: templateContext.rhodecode_user.user_id,
597 user_id: templateContext.rhodecode_user.user_id,
595 user_groups: true,
598 user_groups: true,
596 user_groups_expand: true,
599 user_groups_expand: true,
597 skip_default_user: true
600 skip_default_user: true
598 },
601 },
599 formatResult: autocompleteFormatResult,
602 formatResult: autocompleteFormatResult,
600 lookupFilter: autocompleteFilterResult,
603 lookupFilter: autocompleteFilterResult,
601 onSelect: handler
604 onSelect: handler
602 });
605 });
603 };
606 };
604
607
605 /**
608 /**
606 * Observers autocomplete
609 * Observers autocomplete
607 */
610 */
608 var ObserverAutoComplete = function(inputId, controller) {
611 var ObserverAutoComplete = function(inputId, controller) {
609 var self = this;
612 var self = this;
610 self.controller = controller;
613 self.controller = controller;
611 self.inputId = inputId;
614 self.inputId = inputId;
612 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
615 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
613
616
614 $(inputId).autocomplete({
617 $(inputId).autocomplete({
615 serviceUrl: pyroutes.url('user_autocomplete_data'),
618 serviceUrl: pyroutes.url('user_autocomplete_data'),
616 minChars: 2,
619 minChars: 2,
617 maxHeight: 400,
620 maxHeight: 400,
618 deferRequestBy: 300, //miliseconds
621 deferRequestBy: 300, //miliseconds
619 showNoSuggestionNotice: true,
622 showNoSuggestionNotice: true,
620 tabDisabled: true,
623 tabDisabled: true,
621 autoSelectFirst: true,
624 autoSelectFirst: true,
622 params: {
625 params: {
623 user_id: templateContext.rhodecode_user.user_id,
626 user_id: templateContext.rhodecode_user.user_id,
624 user_groups: true,
627 user_groups: true,
625 user_groups_expand: true,
628 user_groups_expand: true,
626 skip_default_user: true
629 skip_default_user: true
627 },
630 },
628 formatResult: autocompleteFormatResult,
631 formatResult: autocompleteFormatResult,
629 lookupFilter: autocompleteFilterResult,
632 lookupFilter: autocompleteFilterResult,
630 onSelect: handler
633 onSelect: handler
631 });
634 });
632 }
635 }
633
636
634
637
635 window.VersionController = function () {
638 window.VersionController = function () {
636 var self = this;
639 var self = this;
637 this.$verSource = $('input[name=ver_source]');
640 this.$verSource = $('input[name=ver_source]');
638 this.$verTarget = $('input[name=ver_target]');
641 this.$verTarget = $('input[name=ver_target]');
639 this.$showVersionDiff = $('#show-version-diff');
642 this.$showVersionDiff = $('#show-version-diff');
640
643
641 this.adjustRadioSelectors = function (curNode) {
644 this.adjustRadioSelectors = function (curNode) {
642 var getVal = function (item) {
645 var getVal = function (item) {
643 if (item === 'latest') {
646 if (item === 'latest') {
644 return Number.MAX_SAFE_INTEGER
647 return Number.MAX_SAFE_INTEGER
645 }
648 }
646 else {
649 else {
647 return parseInt(item)
650 return parseInt(item)
648 }
651 }
649 };
652 };
650
653
651 var curVal = getVal($(curNode).val());
654 var curVal = getVal($(curNode).val());
652 var cleared = false;
655 var cleared = false;
653
656
654 $.each(self.$verSource, function (index, value) {
657 $.each(self.$verSource, function (index, value) {
655 var elVal = getVal($(value).val());
658 var elVal = getVal($(value).val());
656
659
657 if (elVal > curVal) {
660 if (elVal > curVal) {
658 if ($(value).is(':checked')) {
661 if ($(value).is(':checked')) {
659 cleared = true;
662 cleared = true;
660 }
663 }
661 $(value).attr('disabled', 'disabled');
664 $(value).attr('disabled', 'disabled');
662 $(value).removeAttr('checked');
665 $(value).removeAttr('checked');
663 $(value).css({'opacity': 0.1});
666 $(value).css({'opacity': 0.1});
664 }
667 }
665 else {
668 else {
666 $(value).css({'opacity': 1});
669 $(value).css({'opacity': 1});
667 $(value).removeAttr('disabled');
670 $(value).removeAttr('disabled');
668 }
671 }
669 });
672 });
670
673
671 if (cleared) {
674 if (cleared) {
672 // if we unchecked an active, set the next one to same loc.
675 // if we unchecked an active, set the next one to same loc.
673 $(this.$verSource).filter('[value={0}]'.format(
676 $(this.$verSource).filter('[value={0}]'.format(
674 curVal)).attr('checked', 'checked');
677 curVal)).attr('checked', 'checked');
675 }
678 }
676
679
677 self.setLockAction(false,
680 self.setLockAction(false,
678 $(curNode).data('verPos'),
681 $(curNode).data('verPos'),
679 $(this.$verSource).filter(':checked').data('verPos')
682 $(this.$verSource).filter(':checked').data('verPos')
680 );
683 );
681 };
684 };
682
685
683
686
684 this.attachVersionListener = function () {
687 this.attachVersionListener = function () {
685 self.$verTarget.change(function (e) {
688 self.$verTarget.change(function (e) {
686 self.adjustRadioSelectors(this)
689 self.adjustRadioSelectors(this)
687 });
690 });
688 self.$verSource.change(function (e) {
691 self.$verSource.change(function (e) {
689 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
692 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
690 });
693 });
691 };
694 };
692
695
693 this.init = function () {
696 this.init = function () {
694
697
695 var curNode = self.$verTarget.filter(':checked');
698 var curNode = self.$verTarget.filter(':checked');
696 self.adjustRadioSelectors(curNode);
699 self.adjustRadioSelectors(curNode);
697 self.setLockAction(true);
700 self.setLockAction(true);
698 self.attachVersionListener();
701 self.attachVersionListener();
699
702
700 };
703 };
701
704
702 this.setLockAction = function (state, selectedVersion, otherVersion) {
705 this.setLockAction = function (state, selectedVersion, otherVersion) {
703 var $showVersionDiff = this.$showVersionDiff;
706 var $showVersionDiff = this.$showVersionDiff;
704
707
705 if (state) {
708 if (state) {
706 $showVersionDiff.attr('disabled', 'disabled');
709 $showVersionDiff.attr('disabled', 'disabled');
707 $showVersionDiff.addClass('disabled');
710 $showVersionDiff.addClass('disabled');
708 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
711 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
709 }
712 }
710 else {
713 else {
711 $showVersionDiff.removeAttr('disabled');
714 $showVersionDiff.removeAttr('disabled');
712 $showVersionDiff.removeClass('disabled');
715 $showVersionDiff.removeClass('disabled');
713
716
714 if (selectedVersion == otherVersion) {
717 if (selectedVersion == otherVersion) {
715 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
718 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
716 } else {
719 } else {
717 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
720 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
718 }
721 }
719 }
722 }
720
723
721 };
724 };
722
725
723 this.showVersionDiff = function () {
726 this.showVersionDiff = function () {
724 var target = self.$verTarget.filter(':checked');
727 var target = self.$verTarget.filter(':checked');
725 var source = self.$verSource.filter(':checked');
728 var source = self.$verSource.filter(':checked');
726
729
727 if (target.val() && source.val()) {
730 if (target.val() && source.val()) {
728 var params = {
731 var params = {
729 'pull_request_id': templateContext.pull_request_data.pull_request_id,
732 'pull_request_id': templateContext.pull_request_data.pull_request_id,
730 'repo_name': templateContext.repo_name,
733 'repo_name': templateContext.repo_name,
731 'version': target.val(),
734 'version': target.val(),
732 'from_version': source.val()
735 'from_version': source.val()
733 };
736 };
734 window.location = pyroutes.url('pullrequest_show', params)
737 window.location = pyroutes.url('pullrequest_show', params)
735 }
738 }
736
739
737 return false;
740 return false;
738 };
741 };
739
742
740 this.toggleVersionView = function (elem) {
743 this.toggleVersionView = function (elem) {
741
744
742 if (this.$showVersionDiff.is(':visible')) {
745 if (this.$showVersionDiff.is(':visible')) {
743 $('.version-pr').hide();
746 $('.version-pr').hide();
744 this.$showVersionDiff.hide();
747 this.$showVersionDiff.hide();
745 $(elem).html($(elem).data('toggleOn'))
748 $(elem).html($(elem).data('toggleOn'))
746 } else {
749 } else {
747 $('.version-pr').show();
750 $('.version-pr').show();
748 this.$showVersionDiff.show();
751 this.$showVersionDiff.show();
749 $(elem).html($(elem).data('toggleOff'))
752 $(elem).html($(elem).data('toggleOff'))
750 }
753 }
751
754
752 return false
755 return false
753 };
756 };
754
757
755 };
758 };
756
759
757
760
758 window.UpdatePrController = function () {
761 window.UpdatePrController = function () {
759 var self = this;
762 var self = this;
760 this.$updateCommits = $('#update_commits');
763 this.$updateCommits = $('#update_commits');
761 this.$updateCommitsSwitcher = $('#update_commits_switcher');
764 this.$updateCommitsSwitcher = $('#update_commits_switcher');
762
765
763 this.lockUpdateButton = function (label) {
766 this.lockUpdateButton = function (label) {
764 self.$updateCommits.attr('disabled', 'disabled');
767 self.$updateCommits.attr('disabled', 'disabled');
765 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
768 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
766
769
767 self.$updateCommits.addClass('disabled');
770 self.$updateCommits.addClass('disabled');
768 self.$updateCommitsSwitcher.addClass('disabled');
771 self.$updateCommitsSwitcher.addClass('disabled');
769
772
770 self.$updateCommits.removeClass('btn-primary');
773 self.$updateCommits.removeClass('btn-primary');
771 self.$updateCommitsSwitcher.removeClass('btn-primary');
774 self.$updateCommitsSwitcher.removeClass('btn-primary');
772
775
773 self.$updateCommits.text(_gettext(label));
776 self.$updateCommits.text(_gettext(label));
774 };
777 };
775
778
776 this.isUpdateLocked = function () {
779 this.isUpdateLocked = function () {
777 return self.$updateCommits.attr('disabled') !== undefined;
780 return self.$updateCommits.attr('disabled') !== undefined;
778 };
781 };
779
782
780 this.updateCommits = function (curNode) {
783 this.updateCommits = function (curNode) {
781 if (self.isUpdateLocked()) {
784 if (self.isUpdateLocked()) {
782 return
785 return
783 }
786 }
784 self.lockUpdateButton(_gettext('Updating...'));
787 self.lockUpdateButton(_gettext('Updating...'));
785 updateCommits(
788 updateCommits(
786 templateContext.repo_name,
789 templateContext.repo_name,
787 templateContext.pull_request_data.pull_request_id);
790 templateContext.pull_request_data.pull_request_id);
788 };
791 };
789
792
790 this.forceUpdateCommits = function () {
793 this.forceUpdateCommits = function () {
791 if (self.isUpdateLocked()) {
794 if (self.isUpdateLocked()) {
792 return
795 return
793 }
796 }
794 self.lockUpdateButton(_gettext('Force updating...'));
797 self.lockUpdateButton(_gettext('Force updating...'));
795 var force = true;
798 var force = true;
796 updateCommits(
799 updateCommits(
797 templateContext.repo_name,
800 templateContext.repo_name,
798 templateContext.pull_request_data.pull_request_id, force);
801 templateContext.pull_request_data.pull_request_id, force);
799 };
802 };
800 };
803 };
801
804
802
805
803 /**
806 /**
804 * Reviewer display panel
807 * Reviewer display panel
805 */
808 */
806 window.ReviewersPanel = {
809 window.ReviewersPanel = {
807 editButton: null,
810 editButton: null,
808 closeButton: null,
811 closeButton: null,
809 addButton: null,
812 addButton: null,
810 removeButtons: null,
813 removeButtons: null,
811 reviewRules: null,
814 reviewRules: null,
812 setReviewers: null,
815 setReviewers: null,
813 controller: null,
816 controller: null,
814
817
815 setSelectors: function () {
818 setSelectors: function () {
816 var self = this;
819 var self = this;
817 self.editButton = $('#open_edit_reviewers');
820 self.editButton = $('#open_edit_reviewers');
818 self.closeButton =$('#close_edit_reviewers');
821 self.closeButton =$('#close_edit_reviewers');
819 self.addButton = $('#add_reviewer');
822 self.addButton = $('#add_reviewer');
820 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
823 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
821 },
824 },
822
825
823 init: function (controller, reviewRules, setReviewers) {
826 init: function (controller, reviewRules, setReviewers) {
824 var self = this;
827 var self = this;
825 self.setSelectors();
828 self.setSelectors();
826
829
827 self.controller = controller;
830 self.controller = controller;
828 self.reviewRules = reviewRules;
831 self.reviewRules = reviewRules;
829 self.setReviewers = setReviewers;
832 self.setReviewers = setReviewers;
830
833
831 self.editButton.on('click', function (e) {
834 self.editButton.on('click', function (e) {
832 self.edit();
835 self.edit();
833 });
836 });
834 self.closeButton.on('click', function (e) {
837 self.closeButton.on('click', function (e) {
835 self.close();
838 self.close();
836 self.renderReviewers();
839 self.renderReviewers();
837 });
840 });
838
841
839 self.renderReviewers();
842 self.renderReviewers();
840
843
841 },
844 },
842
845
843 renderReviewers: function () {
846 renderReviewers: function () {
844 var self = this;
847 var self = this;
845
848
846 if (self.setReviewers.reviewers === undefined) {
849 if (self.setReviewers.reviewers === undefined) {
847 return
850 return
848 }
851 }
849 if (self.setReviewers.reviewers.length === 0) {
852 if (self.setReviewers.reviewers.length === 0) {
850 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
853 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
851 return
854 return
852 }
855 }
853
856
854 self.controller.emptyReviewersTable();
857 self.controller.emptyReviewersTable();
855
858
856 $.each(self.setReviewers.reviewers, function (key, val) {
859 $.each(self.setReviewers.reviewers, function (key, val) {
857
860
858 var member = val;
861 var member = val;
859 if (member.role === self.controller.ROLE_REVIEWER) {
862 if (member.role === self.controller.ROLE_REVIEWER) {
860 var entry = renderTemplate('reviewMemberEntry', {
863 var entry = renderTemplate('reviewMemberEntry', {
861 'member': member,
864 'member': member,
862 'mandatory': member.mandatory,
865 'mandatory': member.mandatory,
863 'role': member.role,
866 'role': member.role,
864 'reasons': member.reasons,
867 'reasons': member.reasons,
865 'allowed_to_update': member.allowed_to_update,
868 'allowed_to_update': member.allowed_to_update,
866 'review_status': member.review_status,
869 'review_status': member.review_status,
867 'review_status_label': member.review_status_label,
870 'review_status_label': member.review_status_label,
868 'user_group': member.user_group,
871 'user_group': member.user_group,
869 'create': false
872 'create': false
870 });
873 });
871
874
872 $(self.controller.$reviewMembers.selector).append(entry)
875 $(self.controller.$reviewMembers.selector).append(entry)
873 }
876 }
874 });
877 });
875
878
876 tooltipActivate();
879 tooltipActivate();
877 },
880 },
878
881
879 edit: function (event) {
882 edit: function (event) {
880 var self = this;
883 var self = this;
881 self.editButton.hide();
884 self.editButton.hide();
882 self.closeButton.show();
885 self.closeButton.show();
883 self.addButton.show();
886 self.addButton.show();
884 $(self.removeButtons.selector).css('visibility', 'visible');
887 $(self.removeButtons.selector).css('visibility', 'visible');
885 // review rules
888 // review rules
886 self.controller.loadReviewRules(this.reviewRules);
889 self.controller.loadReviewRules(this.reviewRules);
887 },
890 },
888
891
889 close: function (event) {
892 close: function (event) {
890 var self = this;
893 var self = this;
891 this.editButton.show();
894 this.editButton.show();
892 this.closeButton.hide();
895 this.closeButton.hide();
893 this.addButton.hide();
896 this.addButton.hide();
894 $(this.removeButtons.selector).css('visibility', 'hidden');
897 $(this.removeButtons.selector).css('visibility', 'hidden');
895 // hide review rules
898 // hide review rules
896 self.controller.hideReviewRules();
899 self.controller.hideReviewRules();
897 }
900 }
898 };
901 };
899
902
900 /**
903 /**
901 * Reviewer display panel
904 * Reviewer display panel
902 */
905 */
903 window.ObserversPanel = {
906 window.ObserversPanel = {
904 editButton: null,
907 editButton: null,
905 closeButton: null,
908 closeButton: null,
906 addButton: null,
909 addButton: null,
907 removeButtons: null,
910 removeButtons: null,
908 reviewRules: null,
911 reviewRules: null,
909 setReviewers: null,
912 setReviewers: null,
910 controller: null,
913 controller: null,
911
914
912 setSelectors: function () {
915 setSelectors: function () {
913 var self = this;
916 var self = this;
914 self.editButton = $('#open_edit_observers');
917 self.editButton = $('#open_edit_observers');
915 self.closeButton =$('#close_edit_observers');
918 self.closeButton =$('#close_edit_observers');
916 self.addButton = $('#add_observer');
919 self.addButton = $('#add_observer');
917 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
920 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
918 },
921 },
919
922
920 init: function (controller, reviewRules, setReviewers) {
923 init: function (controller, reviewRules, setReviewers) {
921 var self = this;
924 var self = this;
922 self.setSelectors();
925 self.setSelectors();
923
926
924 self.controller = controller;
927 self.controller = controller;
925 self.reviewRules = reviewRules;
928 self.reviewRules = reviewRules;
926 self.setReviewers = setReviewers;
929 self.setReviewers = setReviewers;
927
930
928 self.editButton.on('click', function (e) {
931 self.editButton.on('click', function (e) {
929 self.edit();
932 self.edit();
930 });
933 });
931 self.closeButton.on('click', function (e) {
934 self.closeButton.on('click', function (e) {
932 self.close();
935 self.close();
933 self.renderObservers();
936 self.renderObservers();
934 });
937 });
935
938
936 self.renderObservers();
939 self.renderObservers();
937
940
938 },
941 },
939
942
940 renderObservers: function () {
943 renderObservers: function () {
941 var self = this;
944 var self = this;
942 if (self.setReviewers.observers === undefined) {
945 if (self.setReviewers.observers === undefined) {
943 return
946 return
944 }
947 }
945 if (self.setReviewers.observers.length === 0) {
948 if (self.setReviewers.observers.length === 0) {
946 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
949 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
947 return
950 return
948 }
951 }
949
952
950 self.controller.emptyObserversTable();
953 self.controller.emptyObserversTable();
951
954
952 $.each(self.setReviewers.observers, function (key, val) {
955 $.each(self.setReviewers.observers, function (key, val) {
953 var member = val;
956 var member = val;
954 if (member.role === self.controller.ROLE_OBSERVER) {
957 if (member.role === self.controller.ROLE_OBSERVER) {
955 var entry = renderTemplate('reviewMemberEntry', {
958 var entry = renderTemplate('reviewMemberEntry', {
956 'member': member,
959 'member': member,
957 'mandatory': member.mandatory,
960 'mandatory': member.mandatory,
958 'role': member.role,
961 'role': member.role,
959 'reasons': member.reasons,
962 'reasons': member.reasons,
960 'allowed_to_update': member.allowed_to_update,
963 'allowed_to_update': member.allowed_to_update,
961 'review_status': member.review_status,
964 'review_status': member.review_status,
962 'review_status_label': member.review_status_label,
965 'review_status_label': member.review_status_label,
963 'user_group': member.user_group,
966 'user_group': member.user_group,
964 'create': false
967 'create': false
965 });
968 });
966
969
967 $(self.controller.$observerMembers.selector).append(entry)
970 $(self.controller.$observerMembers.selector).append(entry)
968 }
971 }
969 });
972 });
970
973
971 tooltipActivate();
974 tooltipActivate();
972 },
975 },
973
976
974 edit: function (event) {
977 edit: function (event) {
975 this.editButton.hide();
978 this.editButton.hide();
976 this.closeButton.show();
979 this.closeButton.show();
977 this.addButton.show();
980 this.addButton.show();
978 $(this.removeButtons.selector).css('visibility', 'visible');
981 $(this.removeButtons.selector).css('visibility', 'visible');
979 },
982 },
980
983
981 close: function (event) {
984 close: function (event) {
982 this.editButton.show();
985 this.editButton.show();
983 this.closeButton.hide();
986 this.closeButton.hide();
984 this.addButton.hide();
987 this.addButton.hide();
985 $(this.removeButtons.selector).css('visibility', 'hidden');
988 $(this.removeButtons.selector).css('visibility', 'hidden');
986 }
989 }
987
990
988 };
991 };
989
992
990 window.PRDetails = {
993 window.PRDetails = {
991 editButton: null,
994 editButton: null,
992 closeButton: null,
995 closeButton: null,
993 deleteButton: null,
996 deleteButton: null,
994 viewFields: null,
997 viewFields: null,
995 editFields: null,
998 editFields: null,
996
999
997 setSelectors: function () {
1000 setSelectors: function () {
998 var self = this;
1001 var self = this;
999 self.editButton = $('#open_edit_pullrequest')
1002 self.editButton = $('#open_edit_pullrequest')
1000 self.closeButton = $('#close_edit_pullrequest')
1003 self.closeButton = $('#close_edit_pullrequest')
1001 self.deleteButton = $('#delete_pullrequest')
1004 self.deleteButton = $('#delete_pullrequest')
1002 self.viewFields = $('#pr-desc, #pr-title')
1005 self.viewFields = $('#pr-desc, #pr-title')
1003 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
1006 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
1004 },
1007 },
1005
1008
1006 init: function () {
1009 init: function () {
1007 var self = this;
1010 var self = this;
1008 self.setSelectors();
1011 self.setSelectors();
1009 self.editButton.on('click', function (e) {
1012 self.editButton.on('click', function (e) {
1010 self.edit();
1013 self.edit();
1011 });
1014 });
1012 self.closeButton.on('click', function (e) {
1015 self.closeButton.on('click', function (e) {
1013 self.view();
1016 self.view();
1014 });
1017 });
1015 },
1018 },
1016
1019
1017 edit: function (event) {
1020 edit: function (event) {
1018 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1021 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1019 this.viewFields.hide();
1022 this.viewFields.hide();
1020 this.editButton.hide();
1023 this.editButton.hide();
1021 this.deleteButton.hide();
1024 this.deleteButton.hide();
1022 this.closeButton.show();
1025 this.closeButton.show();
1023 this.editFields.show();
1026 this.editFields.show();
1024 cmInstance.refresh();
1027 cmInstance.refresh();
1025 },
1028 },
1026
1029
1027 view: function (event) {
1030 view: function (event) {
1028 this.editButton.show();
1031 this.editButton.show();
1029 this.deleteButton.show();
1032 this.deleteButton.show();
1030 this.editFields.hide();
1033 this.editFields.hide();
1031 this.closeButton.hide();
1034 this.closeButton.hide();
1032 this.viewFields.show();
1035 this.viewFields.show();
1033 }
1036 }
1034 };
1037 };
1035
1038
1036 /**
1039 /**
1037 * OnLine presence using channelstream
1040 * OnLine presence using channelstream
1038 */
1041 */
1039 window.ReviewerPresenceController = function (channel) {
1042 window.ReviewerPresenceController = function (channel) {
1040 var self = this;
1043 var self = this;
1041 this.channel = channel;
1044 this.channel = channel;
1042 this.users = {};
1045 this.users = {};
1043
1046
1044 this.storeUsers = function (users) {
1047 this.storeUsers = function (users) {
1045 self.users = {}
1048 self.users = {}
1046 $.each(users, function (index, value) {
1049 $.each(users, function (index, value) {
1047 var userId = value.state.id;
1050 var userId = value.state.id;
1048 self.users[userId] = value.state;
1051 self.users[userId] = value.state;
1049 })
1052 })
1050 }
1053 }
1051
1054
1052 this.render = function () {
1055 this.render = function () {
1053 $.each($('.reviewer_entry'), function (index, value) {
1056 $.each($('.reviewer_entry'), function (index, value) {
1054 var userData = $(value).data();
1057 var userData = $(value).data();
1055 if (self.users[userData.reviewerUserId] !== undefined) {
1058 if (self.users[userData.reviewerUserId] !== undefined) {
1056 $(value).find('.presence-state').show();
1059 $(value).find('.presence-state').show();
1057 } else {
1060 } else {
1058 $(value).find('.presence-state').hide();
1061 $(value).find('.presence-state').hide();
1059 }
1062 }
1060 })
1063 })
1061 };
1064 };
1062
1065
1063 this.handlePresence = function (data) {
1066 this.handlePresence = function (data) {
1064 if (data.type == 'presence' && data.channel === self.channel) {
1067 if (data.type == 'presence' && data.channel === self.channel) {
1065 this.storeUsers(data.users);
1068 this.storeUsers(data.users);
1066 this.render()
1069 this.render()
1067 }
1070 }
1068 };
1071 };
1069
1072
1070 this.handleChannelUpdate = function (data) {
1073 this.handleChannelUpdate = function (data) {
1071 if (data.channel === this.channel) {
1074 if (data.channel === this.channel) {
1072 this.storeUsers(data.state.users);
1075 this.storeUsers(data.state.users);
1073 this.render()
1076 this.render()
1074 }
1077 }
1075
1078
1076 };
1079 };
1077
1080
1078 /* subscribe to the current presence */
1081 /* subscribe to the current presence */
1079 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1082 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1080 /* subscribe to updates e.g connect/disconnect */
1083 /* subscribe to updates e.g connect/disconnect */
1081 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1084 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1082
1085
1083 };
1086 };
1084
1087
1085 window.refreshComments = function (version) {
1088 window.refreshComments = function (version) {
1086 version = version || templateContext.pull_request_data.pull_request_version || '';
1089 version = version || templateContext.pull_request_data.pull_request_version || '';
1087
1090
1088 // Pull request case
1091 // Pull request case
1089 if (templateContext.pull_request_data.pull_request_id !== null) {
1092 if (templateContext.pull_request_data.pull_request_id !== null) {
1090 var params = {
1093 var params = {
1091 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1094 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1092 'repo_name': templateContext.repo_name,
1095 'repo_name': templateContext.repo_name,
1093 'version': version,
1096 'version': version,
1094 };
1097 };
1095 var loadUrl = pyroutes.url('pullrequest_comments', params);
1098 var loadUrl = pyroutes.url('pullrequest_comments', params);
1096 } // commit case
1099 } // commit case
1097 else {
1100 else {
1098 return
1101 return
1099 }
1102 }
1100
1103
1101 var currentIDs = []
1104 var currentIDs = []
1102 $.each($('.comment'), function (idx, element) {
1105 $.each($('.comment'), function (idx, element) {
1103 currentIDs.push($(element).data('commentId'));
1106 currentIDs.push($(element).data('commentId'));
1104 });
1107 });
1105 var data = {"comments": currentIDs};
1108 var data = {"comments": currentIDs};
1106
1109
1107 var $targetElem = $('.comments-content-table');
1110 var $targetElem = $('.comments-content-table');
1108 $targetElem.css('opacity', 0.3);
1111 $targetElem.css('opacity', 0.3);
1109
1112
1110 var success = function (data) {
1113 var success = function (data) {
1111 var $counterElem = $('#comments-count');
1114 var $counterElem = $('#comments-count');
1112 var newCount = $(data).data('counter');
1115 var newCount = $(data).data('counter');
1113 if (newCount !== undefined) {
1116 if (newCount !== undefined) {
1114 var callback = function () {
1117 var callback = function () {
1115 $counterElem.animate({'opacity': 1.00}, 200)
1118 $counterElem.animate({'opacity': 1.00}, 200)
1116 $counterElem.html(newCount);
1119 $counterElem.html(newCount);
1117 };
1120 };
1118 $counterElem.animate({'opacity': 0.15}, 200, callback);
1121 $counterElem.animate({'opacity': 0.15}, 200, callback);
1119 }
1122 }
1120
1123
1121 $targetElem.css('opacity', 1);
1124 $targetElem.css('opacity', 1);
1122 $targetElem.html(data);
1125 $targetElem.html(data);
1123 tooltipActivate();
1126 tooltipActivate();
1124 }
1127 }
1125
1128
1126 ajaxPOST(loadUrl, data, success, null, {})
1129 ajaxPOST(loadUrl, data, success, null, {})
1127
1130
1128 }
1131 }
1129
1132
1130 window.refreshTODOs = function (version) {
1133 window.refreshTODOs = function (version) {
1131 version = version || templateContext.pull_request_data.pull_request_version || '';
1134 version = version || templateContext.pull_request_data.pull_request_version || '';
1132 // Pull request case
1135 // Pull request case
1133 if (templateContext.pull_request_data.pull_request_id !== null) {
1136 if (templateContext.pull_request_data.pull_request_id !== null) {
1134 var params = {
1137 var params = {
1135 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1138 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1136 'repo_name': templateContext.repo_name,
1139 'repo_name': templateContext.repo_name,
1137 'version': version,
1140 'version': version,
1138 };
1141 };
1139 var loadUrl = pyroutes.url('pullrequest_comments', params);
1142 var loadUrl = pyroutes.url('pullrequest_comments', params);
1140 } // commit case
1143 } // commit case
1141 else {
1144 else {
1142 return
1145 return
1143 }
1146 }
1144
1147
1145 var currentIDs = []
1148 var currentIDs = []
1146 $.each($('.comment'), function (idx, element) {
1149 $.each($('.comment'), function (idx, element) {
1147 currentIDs.push($(element).data('commentId'));
1150 currentIDs.push($(element).data('commentId'));
1148 });
1151 });
1149
1152
1150 var data = {"comments": currentIDs};
1153 var data = {"comments": currentIDs};
1151 var $targetElem = $('.todos-content-table');
1154 var $targetElem = $('.todos-content-table');
1152 $targetElem.css('opacity', 0.3);
1155 $targetElem.css('opacity', 0.3);
1153
1156
1154 var success = function (data) {
1157 var success = function (data) {
1155 var $counterElem = $('#todos-count')
1158 var $counterElem = $('#todos-count')
1156 var newCount = $(data).data('counter');
1159 var newCount = $(data).data('counter');
1157 if (newCount !== undefined) {
1160 if (newCount !== undefined) {
1158 var callback = function () {
1161 var callback = function () {
1159 $counterElem.animate({'opacity': 1.00}, 200)
1162 $counterElem.animate({'opacity': 1.00}, 200)
1160 $counterElem.html(newCount);
1163 $counterElem.html(newCount);
1161 };
1164 };
1162 $counterElem.animate({'opacity': 0.15}, 200, callback);
1165 $counterElem.animate({'opacity': 0.15}, 200, callback);
1163 }
1166 }
1164
1167
1165 $targetElem.css('opacity', 1);
1168 $targetElem.css('opacity', 1);
1166 $targetElem.html(data);
1169 $targetElem.html(data);
1167 tooltipActivate();
1170 tooltipActivate();
1168 }
1171 }
1169
1172
1170 ajaxPOST(loadUrl, data, success, null, {})
1173 ajaxPOST(loadUrl, data, success, null, {})
1171
1174
1172 }
1175 }
1173
1176
1174 window.refreshAllComments = function (version) {
1177 window.refreshAllComments = function (version) {
1175 version = version || templateContext.pull_request_data.pull_request_version || '';
1178 version = version || templateContext.pull_request_data.pull_request_version || '';
1176
1179
1177 refreshComments(version);
1180 refreshComments(version);
1178 refreshTODOs(version);
1181 refreshTODOs(version);
1179 };
1182 };
1180
1183
1181 window.sidebarComment = function (commentId) {
1184 window.sidebarComment = function (commentId) {
1182 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1185 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1183 if (!jsonData) {
1186 if (!jsonData) {
1184 return 'Failed to load comment {0}'.format(commentId)
1187 return 'Failed to load comment {0}'.format(commentId)
1185 }
1188 }
1186 var funcData = JSON.parse(atob(jsonData));
1189 var funcData = JSON.parse(atob(jsonData));
1187 return renderTemplate('sideBarCommentHovercard', funcData)
1190 return renderTemplate('sideBarCommentHovercard', funcData)
1188 };
1191 };
@@ -1,642 +1,650 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('New pull request')}
5 ${c.repo_name} ${_('New pull request')}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()"></%def>
8 <%def name="breadcrumbs_links()"></%def>
9
9
10 <%def name="menu_bar_nav()">
10 <%def name="menu_bar_nav()">
11 ${self.menu_items(active='repositories')}
11 ${self.menu_items(active='repositories')}
12 </%def>
12 </%def>
13
13
14 <%def name="menu_bar_subnav()">
14 <%def name="menu_bar_subnav()">
15 ${self.repo_menu(active='showpullrequest')}
15 ${self.repo_menu(active='showpullrequest')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
21
21
22 <div class="box">
22 <div class="box">
23
23
24 <div class="summary-details block-left">
24 <div class="summary-details block-left">
25
25
26 <div class="form" style="padding-top: 10px">
26 <div class="form" style="padding-top: 10px">
27
27
28 <div class="fields" >
28 <div class="fields" >
29
29
30 ## COMMIT FLOW
30 ## COMMIT FLOW
31 <div class="field">
31 <div class="field">
32 <div class="label label-textarea">
32 <div class="label label-textarea">
33 <label for="commit_flow">${_('Commit flow')}:</label>
33 <label for="commit_flow">${_('Commit flow')}:</label>
34 </div>
34 </div>
35
35
36 <div class="content">
36 <div class="content">
37 <div class="flex-container">
37 <div class="flex-container">
38 <div style="width: 45%;">
38 <div style="width: 45%;">
39 <div class="panel panel-default source-panel">
39 <div class="panel panel-default source-panel">
40 <div class="panel-heading">
40 <div class="panel-heading">
41 <h3 class="panel-title">${_('Source repository')}</h3>
41 <h3 class="panel-title">${_('Source repository')}</h3>
42 </div>
42 </div>
43 <div class="panel-body">
43 <div class="panel-body">
44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
45 ${h.hidden('source_repo')}
45 ${h.hidden('source_repo')}
46 ${h.hidden('source_ref')}
46 ${h.hidden('source_ref')}
47
47
48 <div id="pr_open_message"></div>
48 <div id="pr_open_message"></div>
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div style="width: 90px; text-align: center; padding-top: 30px">
53 <div style="width: 90px; text-align: center; padding-top: 30px">
54 <div>
54 <div>
55 <i class="icon-right" style="font-size: 2.2em"></i>
55 <i class="icon-right" style="font-size: 2.2em"></i>
56 </div>
56 </div>
57 <div style="position: relative; top: 10px">
57 <div style="position: relative; top: 10px">
58 <span class="tag tag">
58 <span class="tag tag">
59 <span id="switch_base"></span>
59 <span id="switch_base"></span>
60 </span>
60 </span>
61 </div>
61 </div>
62
62
63 </div>
63 </div>
64
64
65 <div style="width: 45%;">
65 <div style="width: 45%;">
66
66
67 <div class="panel panel-default target-panel">
67 <div class="panel panel-default target-panel">
68 <div class="panel-heading">
68 <div class="panel-heading">
69 <h3 class="panel-title">${_('Target repository')}</h3>
69 <h3 class="panel-title">${_('Target repository')}</h3>
70 </div>
70 </div>
71 <div class="panel-body">
71 <div class="panel-body">
72 <div style="display:none" id="target_repo_desc"></div>
72 <div style="display:none" id="target_repo_desc"></div>
73 ${h.hidden('target_repo')}
73 ${h.hidden('target_repo')}
74 ${h.hidden('target_ref')}
74 ${h.hidden('target_ref')}
75 <span id="target_ref_loading" style="display: none">
75 <span id="target_ref_loading" style="display: none">
76 ${_('Loading refs...')}
76 ${_('Loading refs...')}
77 </span>
77 </span>
78 </div>
78 </div>
79 </div>
79 </div>
80
80
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 </div>
84 </div>
85
85
86 </div>
86 </div>
87
87
88 ## TITLE
88 ## TITLE
89 <div class="field">
89 <div class="field">
90 <div class="label">
90 <div class="label">
91 <label for="pullrequest_title">${_('Title')}:</label>
91 <label for="pullrequest_title">${_('Title')}:</label>
92 </div>
92 </div>
93 <div class="input">
93 <div class="input">
94 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
94 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
95 </div>
95 </div>
96 <p class="help-block">
96 <p class="help-block">
97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
98 </p>
98 </p>
99 </div>
99 </div>
100
100
101 ## DESC
101 ## DESC
102 <div class="field">
102 <div class="field">
103 <div class="label label-textarea">
103 <div class="label label-textarea">
104 <label for="pullrequest_desc">${_('Description')}:</label>
104 <label for="pullrequest_desc">${_('Description')}:</label>
105 </div>
105 </div>
106 <div class="textarea text-area">
106 <div class="textarea text-area">
107 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
107 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
108 ${dt.markup_form('pullrequest_desc')}
108 ${dt.markup_form('pullrequest_desc')}
109 </div>
109 </div>
110 </div>
110 </div>
111
111
112 ## REVIEWERS
112 ## REVIEWERS
113 <div class="field">
113 <div class="field">
114 <div class="label label-textarea">
114 <div class="label label-textarea">
115 <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
115 <label for="pullrequest_reviewers">${_('Reviewers / Observers')}:</label>
116 </div>
116 </div>
117 <div class="content">
117 <div class="content">
118 ## REVIEW RULES
118 ## REVIEW RULES
119 <div id="review_rules" style="display: none" class="reviewers-title">
119 <div id="review_rules" style="display: none" class="reviewers-title">
120 <div class="pr-details-title">
120 <div class="pr-details-title">
121 ${_('Reviewer rules')}
121 ${_('Reviewer rules')}
122 </div>
122 </div>
123 <div class="pr-reviewer-rules">
123 <div class="pr-reviewer-rules">
124 ## review rules will be appended here, by default reviewers logic
124 ## review rules will be appended here, by default reviewers logic
125 </div>
125 </div>
126 </div>
126 </div>
127
127
128 ## REVIEWERS / OBSERVERS
128 ## REVIEWERS / OBSERVERS
129 <div class="reviewers-title">
129 <div class="reviewers-title">
130
130
131 <ul class="nav-links clearfix">
131 <ul class="nav-links clearfix">
132
132
133 ## TAB1 MANDATORY REVIEWERS
133 ## TAB1 MANDATORY REVIEWERS
134 <li class="active">
134 <li class="active">
135 <a id="reviewers-btn" href="#showReviewers" tabindex="-1">
135 <a id="reviewers-btn" href="#showReviewers" tabindex="-1">
136 Reviewers
136 Reviewers
137 <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
137 <span id="reviewers-cnt" data-count="0" class="menulink-counter">0</span>
138 </a>
138 </a>
139 </li>
139 </li>
140
140
141 ## TAB2 OBSERVERS
141 ## TAB2 OBSERVERS
142 <li class="">
142 <li class="">
143 <a id="observers-btn" href="#showObservers" tabindex="-1">
143 <a id="observers-btn" href="#showObservers" tabindex="-1">
144 Observers
144 Observers
145 <span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
145 <span id="observers-cnt" data-count="0" class="menulink-counter">0</span>
146 </a>
146 </a>
147 </li>
147 </li>
148
148
149 </ul>
149 </ul>
150
150
151 ## TAB1 MANDATORY REVIEWERS
151 ## TAB1 MANDATORY REVIEWERS
152 <div id="reviewers-container">
152 <div id="reviewers-container">
153 <span class="calculate-reviewers">
153 <span class="calculate-reviewers">
154 <h4>${_('loading...')}</h4>
154 <h4>${_('loading...')}</h4>
155 </span>
155 </span>
156
156
157 <div id="reviewers" class="pr-details-content reviewers">
157 <div id="reviewers" class="pr-details-content reviewers">
158 ## members goes here, filled via JS based on initial selection !
158 ## members goes here, filled via JS based on initial selection !
159 <input type="hidden" name="__start__" value="review_members:sequence">
159 <input type="hidden" name="__start__" value="review_members:sequence">
160 <table id="review_members" class="group_members">
160 <table id="review_members" class="group_members">
161 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
161 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
162 </table>
162 </table>
163 <input type="hidden" name="__end__" value="review_members:sequence">
163 <input type="hidden" name="__end__" value="review_members:sequence">
164
164
165 <div id="add_reviewer_input" class='ac'>
165 <div id="add_reviewer_input" class='ac'>
166 <div class="reviewer_ac">
166 <div class="reviewer_ac">
167 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
167 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
168 <div id="reviewers_container"></div>
168 <div id="reviewers_container"></div>
169 </div>
169 </div>
170 </div>
170 </div>
171
171
172 </div>
172 </div>
173 </div>
173 </div>
174
174
175 ## TAB2 OBSERVERS
175 ## TAB2 OBSERVERS
176 <div id="observers-container" style="display: none">
176 <div id="observers-container" style="display: none">
177 <span class="calculate-reviewers">
177 <span class="calculate-reviewers">
178 <h4>${_('loading...')}</h4>
178 <h4>${_('loading...')}</h4>
179 </span>
179 </span>
180 % if c.rhodecode_edition_id == 'EE':
180 % if c.rhodecode_edition_id == 'EE':
181 <div id="observers" class="pr-details-content observers">
181 <div id="observers" class="pr-details-content observers">
182 ## members goes here, filled via JS based on initial selection !
182 ## members goes here, filled via JS based on initial selection !
183 <input type="hidden" name="__start__" value="observer_members:sequence">
183 <input type="hidden" name="__start__" value="observer_members:sequence">
184 <table id="observer_members" class="group_members">
184 <table id="observer_members" class="group_members">
185 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
185 ## This content is loaded via JS and ReviewersPanel, an sets reviewer_entry class on each element
186 </table>
186 </table>
187 <input type="hidden" name="__end__" value="observer_members:sequence">
187 <input type="hidden" name="__end__" value="observer_members:sequence">
188
188
189 <div id="add_observer_input" class='ac'>
189 <div id="add_observer_input" class='ac'>
190 <div class="observer_ac">
190 <div class="observer_ac">
191 ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
191 ${h.text('observer', class_='ac-input', placeholder=_('Add observer or observer group'))}
192 <div id="observers_container"></div>
192 <div id="observers_container"></div>
193 </div>
193 </div>
194 </div>
194 </div>
195 </div>
195 </div>
196 % else:
196 % else:
197 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
197 <h4>${_('This feature is available in RhodeCode EE edition only. Contact {sales_email} to obtain a trial license.').format(sales_email='<a href="mailto:sales@rhodecode.com">sales@rhodecode.com</a>')|n}</h4>
198 <p>
198 <p>
199 Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes.
199 Pull request observers allows adding users who don't need to leave mandatory votes, but need to be aware about certain changes.
200 </p>
200 </p>
201 % endif
201 % endif
202 </div>
202 </div>
203
203
204 </div>
204 </div>
205
205
206 </div>
206 </div>
207 </div>
207 </div>
208
208
209 ## SUBMIT
209 ## SUBMIT
210 <div class="field">
210 <div class="field">
211 <div class="label label-textarea">
211 <div class="label label-textarea">
212 <label for="pullrequest_submit"></label>
212 <label for="pullrequest_submit"></label>
213 </div>
213 </div>
214 <div class="input">
214 <div class="input">
215 <div class="pr-submit-button">
215 <div class="pr-submit-button">
216 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
216 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
217 </div>
217 </div>
218 </div>
218 </div>
219 </div>
219 </div>
220 </div>
220 </div>
221 </div>
221 </div>
222 </div>
222 </div>
223
223
224 </div>
224 </div>
225
225
226 ${h.end_form()}
226 ${h.end_form()}
227 </div>
227 </div>
228
228
229 <script type="text/javascript">
229 <script type="text/javascript">
230 $(function(){
230 $(function(){
231 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
231 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
232 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
232 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
233 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
233 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
234 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
234 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
235
235
236 var $pullRequestForm = $('#pull_request_form');
236 var $pullRequestForm = $('#pull_request_form');
237 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
237 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
238 var $sourceRepo = $('#source_repo', $pullRequestForm);
238 var $sourceRepo = $('#source_repo', $pullRequestForm);
239 var $targetRepo = $('#target_repo', $pullRequestForm);
239 var $targetRepo = $('#target_repo', $pullRequestForm);
240 var $sourceRef = $('#source_ref', $pullRequestForm);
240 var $sourceRef = $('#source_ref', $pullRequestForm);
241 var $targetRef = $('#target_ref', $pullRequestForm);
241 var $targetRef = $('#target_ref', $pullRequestForm);
242
242
243 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
243 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
244 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
244 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
245
245
246 var targetRepo = function() { return $targetRepo.eq(0).val() };
246 var targetRepo = function() { return $targetRepo.eq(0).val() };
247 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
247 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
248
248
249 var calculateContainerWidth = function() {
249 var calculateContainerWidth = function() {
250 var maxWidth = 0;
250 var maxWidth = 0;
251 var repoSelect2Containers = ['#source_repo', '#target_repo'];
251 var repoSelect2Containers = ['#source_repo', '#target_repo'];
252 $.each(repoSelect2Containers, function(idx, value) {
252 $.each(repoSelect2Containers, function(idx, value) {
253 $(value).select2('container').width('auto');
253 $(value).select2('container').width('auto');
254 var curWidth = $(value).select2('container').width();
254 var curWidth = $(value).select2('container').width();
255 if (maxWidth <= curWidth) {
255 if (maxWidth <= curWidth) {
256 maxWidth = curWidth;
256 maxWidth = curWidth;
257 }
257 }
258 $.each(repoSelect2Containers, function(idx, value) {
258 $.each(repoSelect2Containers, function(idx, value) {
259 $(value).select2('container').width(maxWidth + 10);
259 $(value).select2('container').width(maxWidth + 10);
260 });
260 });
261 });
261 });
262 };
262 };
263
263
264 var initRefSelection = function(selectedRef) {
264 var initRefSelection = function(selectedRef) {
265 return function(element, callback) {
265 return function(element, callback) {
266 // translate our select2 id into a text, it's a mapping to show
266 // translate our select2 id into a text, it's a mapping to show
267 // simple label when selecting by internal ID.
267 // simple label when selecting by internal ID.
268 var id, refData;
268 var id, refData;
269 if (selectedRef === undefined || selectedRef === null) {
269 if (selectedRef === undefined || selectedRef === null) {
270 id = element.val();
270 id = element.val();
271 refData = element.val().split(':');
271 refData = element.val().split(':');
272
272
273 if (refData.length !== 3){
273 if (refData.length !== 3){
274 refData = ["", "", ""]
274 refData = ["", "", ""]
275 }
275 }
276 } else {
276 } else {
277 id = selectedRef;
277 id = selectedRef;
278 refData = selectedRef.split(':');
278 refData = selectedRef.split(':');
279 }
279 }
280
280
281 var text = refData[1];
281 var text = refData[1];
282 if (refData[0] === 'rev') {
282 if (refData[0] === 'rev') {
283 text = text.substring(0, 12);
283 text = text.substring(0, 12);
284 }
284 }
285
285
286 var data = {id: id, text: text};
286 var data = {id: id, text: text};
287 callback(data);
287 callback(data);
288 };
288 };
289 };
289 };
290
290
291 var formatRefSelection = function(data, container, escapeMarkup) {
291 var formatRefSelection = function(data, container, escapeMarkup) {
292 var prefix = '';
292 var prefix = '';
293 var refData = data.id.split(':');
293 var refData = data.id.split(':');
294 if (refData[0] === 'branch') {
294 if (refData[0] === 'branch') {
295 prefix = '<i class="icon-branch"></i>';
295 prefix = '<i class="icon-branch"></i>';
296 }
296 }
297 else if (refData[0] === 'book') {
297 else if (refData[0] === 'book') {
298 prefix = '<i class="icon-bookmark"></i>';
298 prefix = '<i class="icon-bookmark"></i>';
299 }
299 }
300 else if (refData[0] === 'tag') {
300 else if (refData[0] === 'tag') {
301 prefix = '<i class="icon-tag"></i>';
301 prefix = '<i class="icon-tag"></i>';
302 }
302 }
303
303
304 var originalOption = data.element;
304 var originalOption = data.element;
305 return prefix + escapeMarkup(data.text);
305 return prefix + escapeMarkup(data.text);
306 };
306 };
307
307
308 // custom code mirror
308 // custom code mirror
309 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
309 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
310
310
311 var diffDataHandler = function(data) {
311 var diffDataHandler = function(data) {
312 if (data['error'] !== undefined) {
313 var noCommitsMsg = '<span class="alert-text-error">{0}</span>'.format(data['error']);
314 prButtonLock(true, noCommitsMsg, 'compare');
315 //make both panels equal
316 $('.target-panel').height($('.source-panel').height())
317 return false
318 }
312
319
313 var commitElements = data['commits'];
320 var commitElements = data['commits'];
314 var files = data['files'];
321 var files = data['files'];
315 var added = data['stats'][0]
322 var added = data['stats'][0]
316 var deleted = data['stats'][1]
323 var deleted = data['stats'][1]
317 var commonAncestorId = data['ancestor'];
324 var commonAncestorId = data['ancestor'];
318 var _sourceRefType = sourceRef()[0];
325 var _sourceRefType = sourceRef()[0];
319 var _sourceRefName = sourceRef()[1];
326 var _sourceRefName = sourceRef()[1];
320 var prTitleAndDesc = getTitleAndDescription(_sourceRefType, _sourceRefName, commitElements, 5);
327 var prTitleAndDesc = getTitleAndDescription(_sourceRefType, _sourceRefName, commitElements, 5);
321
328
322 var title = prTitleAndDesc[0];
329 var title = prTitleAndDesc[0];
323 var proposedDescription = prTitleAndDesc[1];
330 var proposedDescription = prTitleAndDesc[1];
324
331
325 var useGeneratedTitle = (
332 var useGeneratedTitle = (
326 $('#pullrequest_title').hasClass('autogenerated-title') ||
333 $('#pullrequest_title').hasClass('autogenerated-title') ||
327 $('#pullrequest_title').val() === "");
334 $('#pullrequest_title').val() === "");
328
335
329 if (title && useGeneratedTitle) {
336 if (title && useGeneratedTitle) {
330 // use generated title if we haven't specified our own
337 // use generated title if we haven't specified our own
331 $('#pullrequest_title').val(title);
338 $('#pullrequest_title').val(title);
332 $('#pullrequest_title').addClass('autogenerated-title');
339 $('#pullrequest_title').addClass('autogenerated-title');
333
340
334 }
341 }
335
342
336 var useGeneratedDescription = (
343 var useGeneratedDescription = (
337 !codeMirrorInstance._userDefinedValue ||
344 !codeMirrorInstance._userDefinedValue ||
338 codeMirrorInstance.getValue() === "");
345 codeMirrorInstance.getValue() === "");
339
346
340 if (proposedDescription && useGeneratedDescription) {
347 if (proposedDescription && useGeneratedDescription) {
341 // set proposed content, if we haven't defined our own,
348 // set proposed content, if we haven't defined our own,
342 // or we don't have description written
349 // or we don't have description written
343 codeMirrorInstance._userDefinedValue = false; // reset state
350 codeMirrorInstance._userDefinedValue = false; // reset state
344 codeMirrorInstance.setValue(proposedDescription);
351 codeMirrorInstance.setValue(proposedDescription);
345 }
352 }
346
353
347 // refresh our codeMirror so events kicks in and it's change aware
354 // refresh our codeMirror so events kicks in and it's change aware
348 codeMirrorInstance.refresh();
355 codeMirrorInstance.refresh();
349
356
350 var url_data = {
357 var url_data = {
351 'repo_name': targetRepo(),
358 'repo_name': targetRepo(),
352 'target_repo': sourceRepo(),
359 'target_repo': sourceRepo(),
353 'source_ref': targetRef()[2],
360 'source_ref': targetRef()[2],
354 'source_ref_type': 'rev',
361 'source_ref_type': 'rev',
355 'target_ref': sourceRef()[2],
362 'target_ref': sourceRef()[2],
356 'target_ref_type': 'rev',
363 'target_ref_type': 'rev',
357 'merge': true,
364 'merge': true,
358 '_': Date.now() // bypass browser caching
365 '_': Date.now() // bypass browser caching
359 }; // gather the source/target ref and repo here
366 }; // gather the source/target ref and repo here
360 var url = pyroutes.url('repo_compare', url_data);
367 var url = pyroutes.url('repo_compare', url_data);
361
368
362 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
369 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
363 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
370 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
364
371
365
372
366 $.each(commitElements, function(idx, value) {
373 $.each(commitElements, function(idx, value) {
367 var commit_id = value["commit_id"]
374 var commit_id = value["commit_id"]
368 msg += '<input type="hidden" name="revisions" value="{0}">'.format(commit_id);
375 msg += '<input type="hidden" name="revisions" value="{0}">'.format(commit_id);
369 });
376 });
370
377
371 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
378 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
372 msg += _ngettext(
379 msg += _ngettext(
373 'Compare summary: <strong>{0} commit</strong>',
380 'Compare summary: <strong>{0} commit</strong>',
374 'Compare summary: <strong>{0} commits</strong>',
381 'Compare summary: <strong>{0} commits</strong>',
375 commitElements.length).format(commitElements.length)
382 commitElements.length).format(commitElements.length)
376
383
377 msg += '';
384 msg += '';
378 msg += _ngettext(
385 msg += _ngettext(
379 '<strong>, and {0} file</strong> changed.',
386 '<strong>, and {0} file</strong> changed.',
380 '<strong>, and {0} files</strong> changed.',
387 '<strong>, and {0} files</strong> changed.',
381 files.length).format(files.length)
388 files.length).format(files.length)
382
389
383 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
390 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
384
391
385 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
392 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
386
393
387 if (commitElements.length) {
394 if (commitElements.length) {
388 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
395 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
389 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
396 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
390 }
397 }
391 else {
398 else {
392 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
399 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
393 _gettext('There are no commits to merge.'));
400 _gettext('There are no commits to merge.'));
394 prButtonLock(true, noCommitsMsg, 'compare');
401 prButtonLock(true, noCommitsMsg, 'compare');
395 }
402 }
396
403
397 //make both panels equal
404 //make both panels equal
398 $('.target-panel').height($('.source-panel').height())
405 $('.target-panel').height($('.source-panel').height());
406 return true
399 };
407 };
400
408
401 reviewersController = new ReviewersController();
409 reviewersController = new ReviewersController();
402 reviewersController.diffDataHandler = diffDataHandler;
410 reviewersController.diffDataHandler = diffDataHandler;
403
411
404 var queryTargetRepo = function(self, query) {
412 var queryTargetRepo = function(self, query) {
405 // cache ALL results if query is empty
413 // cache ALL results if query is empty
406 var cacheKey = query.term || '__';
414 var cacheKey = query.term || '__';
407 var cachedData = self.cachedDataSource[cacheKey];
415 var cachedData = self.cachedDataSource[cacheKey];
408
416
409 if (cachedData) {
417 if (cachedData) {
410 query.callback({results: cachedData.results});
418 query.callback({results: cachedData.results});
411 } else {
419 } else {
412 $.ajax({
420 $.ajax({
413 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
421 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
414 data: {query: query.term},
422 data: {query: query.term},
415 dataType: 'json',
423 dataType: 'json',
416 type: 'GET',
424 type: 'GET',
417 success: function(data) {
425 success: function(data) {
418 self.cachedDataSource[cacheKey] = data;
426 self.cachedDataSource[cacheKey] = data;
419 query.callback({results: data.results});
427 query.callback({results: data.results});
420 },
428 },
421 error: function(jqXHR, textStatus, errorThrown) {
429 error: function(jqXHR, textStatus, errorThrown) {
422 var prefix = "Error while fetching entries.\n"
430 var prefix = "Error while fetching entries.\n"
423 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
431 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
424 ajaxErrorSwal(message);
432 ajaxErrorSwal(message);
425 }
433 }
426 });
434 });
427 }
435 }
428 };
436 };
429
437
430 var queryTargetRefs = function(initialData, query) {
438 var queryTargetRefs = function(initialData, query) {
431 var data = {results: []};
439 var data = {results: []};
432 // filter initialData
440 // filter initialData
433 $.each(initialData, function() {
441 $.each(initialData, function() {
434 var section = this.text;
442 var section = this.text;
435 var children = [];
443 var children = [];
436 $.each(this.children, function() {
444 $.each(this.children, function() {
437 if (query.term.length === 0 ||
445 if (query.term.length === 0 ||
438 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
446 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
439 children.push({'id': this.id, 'text': this.text})
447 children.push({'id': this.id, 'text': this.text})
440 }
448 }
441 });
449 });
442 data.results.push({'text': section, 'children': children})
450 data.results.push({'text': section, 'children': children})
443 });
451 });
444 query.callback({results: data.results});
452 query.callback({results: data.results});
445 };
453 };
446
454
447 var Select2Box = function(element, overrides) {
455 var Select2Box = function(element, overrides) {
448 var globalDefaults = {
456 var globalDefaults = {
449 dropdownAutoWidth: true,
457 dropdownAutoWidth: true,
450 containerCssClass: "drop-menu",
458 containerCssClass: "drop-menu",
451 dropdownCssClass: "drop-menu-dropdown"
459 dropdownCssClass: "drop-menu-dropdown"
452 };
460 };
453
461
454 var initSelect2 = function(defaultOptions) {
462 var initSelect2 = function(defaultOptions) {
455 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
463 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
456 element.select2(options);
464 element.select2(options);
457 };
465 };
458
466
459 return {
467 return {
460 initRef: function() {
468 initRef: function() {
461 var defaultOptions = {
469 var defaultOptions = {
462 minimumResultsForSearch: 5,
470 minimumResultsForSearch: 5,
463 formatSelection: formatRefSelection
471 formatSelection: formatRefSelection
464 };
472 };
465
473
466 initSelect2(defaultOptions);
474 initSelect2(defaultOptions);
467 },
475 },
468
476
469 initRepo: function(defaultValue, readOnly) {
477 initRepo: function(defaultValue, readOnly) {
470 var defaultOptions = {
478 var defaultOptions = {
471 initSelection : function (element, callback) {
479 initSelection : function (element, callback) {
472 var data = {id: defaultValue, text: defaultValue};
480 var data = {id: defaultValue, text: defaultValue};
473 callback(data);
481 callback(data);
474 }
482 }
475 };
483 };
476
484
477 initSelect2(defaultOptions);
485 initSelect2(defaultOptions);
478
486
479 element.select2('val', defaultSourceRepo);
487 element.select2('val', defaultSourceRepo);
480 if (readOnly === true) {
488 if (readOnly === true) {
481 element.select2('readonly', true);
489 element.select2('readonly', true);
482 }
490 }
483 }
491 }
484 };
492 };
485 };
493 };
486
494
487 var initTargetRefs = function(refsData, selectedRef) {
495 var initTargetRefs = function(refsData, selectedRef) {
488
496
489 Select2Box($targetRef, {
497 Select2Box($targetRef, {
490 placeholder: "${_('Select commit reference')}",
498 placeholder: "${_('Select commit reference')}",
491 query: function(query) {
499 query: function(query) {
492 queryTargetRefs(refsData, query);
500 queryTargetRefs(refsData, query);
493 },
501 },
494 initSelection : initRefSelection(selectedRef)
502 initSelection : initRefSelection(selectedRef)
495 }).initRef();
503 }).initRef();
496
504
497 if (!(selectedRef === undefined)) {
505 if (!(selectedRef === undefined)) {
498 $targetRef.select2('val', selectedRef);
506 $targetRef.select2('val', selectedRef);
499 }
507 }
500 };
508 };
501
509
502 var targetRepoChanged = function(repoData) {
510 var targetRepoChanged = function(repoData) {
503 // generate new DESC of target repo displayed next to select
511 // generate new DESC of target repo displayed next to select
504
512
505 $('#target_repo_desc').html(repoData['description']);
513 $('#target_repo_desc').html(repoData['description']);
506
514
507 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
515 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
508 var title = _gettext('Switch target repository with the source.')
516 var title = _gettext('Switch target repository with the source.')
509 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
517 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
510
518
511 // generate dynamic select2 for refs.
519 // generate dynamic select2 for refs.
512 initTargetRefs(repoData['refs']['select2_refs'],
520 initTargetRefs(repoData['refs']['select2_refs'],
513 repoData['refs']['selected_ref']);
521 repoData['refs']['selected_ref']);
514
522
515 };
523 };
516
524
517 var sourceRefSelect2 = Select2Box($sourceRef, {
525 var sourceRefSelect2 = Select2Box($sourceRef, {
518 placeholder: "${_('Select commit reference')}",
526 placeholder: "${_('Select commit reference')}",
519 query: function(query) {
527 query: function(query) {
520 var initialData = defaultSourceRepoData['refs']['select2_refs'];
528 var initialData = defaultSourceRepoData['refs']['select2_refs'];
521 queryTargetRefs(initialData, query)
529 queryTargetRefs(initialData, query)
522 },
530 },
523 initSelection: initRefSelection()
531 initSelection: initRefSelection()
524 });
532 });
525
533
526 var sourceRepoSelect2 = Select2Box($sourceRepo, {
534 var sourceRepoSelect2 = Select2Box($sourceRepo, {
527 query: function(query) {}
535 query: function(query) {}
528 });
536 });
529
537
530 var targetRepoSelect2 = Select2Box($targetRepo, {
538 var targetRepoSelect2 = Select2Box($targetRepo, {
531 cachedDataSource: {},
539 cachedDataSource: {},
532 query: $.debounce(250, function(query) {
540 query: $.debounce(250, function(query) {
533 queryTargetRepo(this, query);
541 queryTargetRepo(this, query);
534 }),
542 }),
535 formatResult: formatRepoResult
543 formatResult: formatRepoResult
536 });
544 });
537
545
538 sourceRefSelect2.initRef();
546 sourceRefSelect2.initRef();
539
547
540 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
548 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
541
549
542 targetRepoSelect2.initRepo(defaultTargetRepo, false);
550 targetRepoSelect2.initRepo(defaultTargetRepo, false);
543
551
544 $sourceRef.on('change', function(e){
552 $sourceRef.on('change', function(e){
545 reviewersController.loadDefaultReviewers(
553 reviewersController.loadDefaultReviewers(
546 sourceRepo(), sourceRef(), targetRepo(), targetRef());
554 sourceRepo(), sourceRef(), targetRepo(), targetRef());
547 });
555 });
548
556
549 $targetRef.on('change', function(e){
557 $targetRef.on('change', function(e){
550 reviewersController.loadDefaultReviewers(
558 reviewersController.loadDefaultReviewers(
551 sourceRepo(), sourceRef(), targetRepo(), targetRef());
559 sourceRepo(), sourceRef(), targetRepo(), targetRef());
552 });
560 });
553
561
554 $targetRepo.on('change', function(e){
562 $targetRepo.on('change', function(e){
555 var repoName = $(this).val();
563 var repoName = $(this).val();
556 calculateContainerWidth();
564 calculateContainerWidth();
557 $targetRef.select2('destroy');
565 $targetRef.select2('destroy');
558 $('#target_ref_loading').show();
566 $('#target_ref_loading').show();
559
567
560 $.ajax({
568 $.ajax({
561 url: pyroutes.url('pullrequest_repo_refs',
569 url: pyroutes.url('pullrequest_repo_refs',
562 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
570 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
563 data: {},
571 data: {},
564 dataType: 'json',
572 dataType: 'json',
565 type: 'GET',
573 type: 'GET',
566 success: function(data) {
574 success: function(data) {
567 $('#target_ref_loading').hide();
575 $('#target_ref_loading').hide();
568 targetRepoChanged(data);
576 targetRepoChanged(data);
569 },
577 },
570 error: function(jqXHR, textStatus, errorThrown) {
578 error: function(jqXHR, textStatus, errorThrown) {
571 var prefix = "Error while fetching entries.\n"
579 var prefix = "Error while fetching entries.\n"
572 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
580 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
573 ajaxErrorSwal(message);
581 ajaxErrorSwal(message);
574 }
582 }
575 })
583 })
576
584
577 });
585 });
578
586
579 $pullRequestForm.on('submit', function(e){
587 $pullRequestForm.on('submit', function(e){
580 // Flush changes into textarea
588 // Flush changes into textarea
581 codeMirrorInstance.save();
589 codeMirrorInstance.save();
582 prButtonLock(true, null, 'all');
590 prButtonLock(true, null, 'all');
583 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
591 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
584 });
592 });
585
593
586 prButtonLock(true, "${_('Please select source and target')}", 'all');
594 prButtonLock(true, "${_('Please select source and target')}", 'all');
587
595
588 // auto-load on init, the target refs select2
596 // auto-load on init, the target refs select2
589 calculateContainerWidth();
597 calculateContainerWidth();
590 targetRepoChanged(defaultTargetRepoData);
598 targetRepoChanged(defaultTargetRepoData);
591
599
592 $('#pullrequest_title').on('keyup', function(e){
600 $('#pullrequest_title').on('keyup', function(e){
593 $(this).removeClass('autogenerated-title');
601 $(this).removeClass('autogenerated-title');
594 });
602 });
595
603
596 % if c.default_source_ref:
604 % if c.default_source_ref:
597 // in case we have a pre-selected value, use it now
605 // in case we have a pre-selected value, use it now
598 $sourceRef.select2('val', '${c.default_source_ref}');
606 $sourceRef.select2('val', '${c.default_source_ref}');
599
607
600
608
601 // default reviewers / observers
609 // default reviewers / observers
602 reviewersController.loadDefaultReviewers(
610 reviewersController.loadDefaultReviewers(
603 sourceRepo(), sourceRef(), targetRepo(), targetRef());
611 sourceRepo(), sourceRef(), targetRepo(), targetRef());
604 % endif
612 % endif
605
613
606 ReviewerAutoComplete('#user', reviewersController);
614 ReviewerAutoComplete('#user', reviewersController);
607 ObserverAutoComplete('#observer', reviewersController);
615 ObserverAutoComplete('#observer', reviewersController);
608
616
609 // TODO, move this to another handler
617 // TODO, move this to another handler
610
618
611 var $reviewersBtn = $('#reviewers-btn');
619 var $reviewersBtn = $('#reviewers-btn');
612 var $reviewersContainer = $('#reviewers-container');
620 var $reviewersContainer = $('#reviewers-container');
613
621
614 var $observersBtn = $('#observers-btn')
622 var $observersBtn = $('#observers-btn')
615 var $observersContainer = $('#observers-container');
623 var $observersContainer = $('#observers-container');
616
624
617 $reviewersBtn.on('click', function (e) {
625 $reviewersBtn.on('click', function (e) {
618
626
619 $observersContainer.hide();
627 $observersContainer.hide();
620 $reviewersContainer.show();
628 $reviewersContainer.show();
621
629
622 $observersBtn.parent().removeClass('active');
630 $observersBtn.parent().removeClass('active');
623 $reviewersBtn.parent().addClass('active');
631 $reviewersBtn.parent().addClass('active');
624 e.preventDefault();
632 e.preventDefault();
625
633
626 })
634 })
627
635
628 $observersBtn.on('click', function (e) {
636 $observersBtn.on('click', function (e) {
629
637
630 $reviewersContainer.hide();
638 $reviewersContainer.hide();
631 $observersContainer.show();
639 $observersContainer.show();
632
640
633 $reviewersBtn.parent().removeClass('active');
641 $reviewersBtn.parent().removeClass('active');
634 $observersBtn.parent().addClass('active');
642 $observersBtn.parent().addClass('active');
635 e.preventDefault();
643 e.preventDefault();
636
644
637 })
645 })
638
646
639 });
647 });
640 </script>
648 </script>
641
649
642 </%def>
650 </%def>
General Comments 0
You need to be logged in to leave comments. Login now