##// END OF EJS Templates
default-reviewers: handle no common ancestor case.
marcink -
r4520:10c5ceba stable
parent child
Show More
@@ -1,82 +1,89
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 review_data = get_default_reviewers_data(
75 try:
76 current_user,
76 review_data = get_default_reviewers_data(
77 source_repo,
77 current_user,
78 Reference(source_type, source_name, source_commit_id),
78 source_repo,
79 target_repo,
79 Reference(source_type, source_name, source_commit_id),
80 Reference(target_type, target_name, target_commit_id)
80 target_repo,
81 )
81 Reference(target_type, target_name, target_commit_id)
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
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':