##// END OF EJS Templates
pull-requests: added creating indicator to let users know they should wait....
marcink -
r4231:aed57628 stable
parent child Browse files
Show More
@@ -1,543 +1,544 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('New pull request')}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()"></%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="menu_bar_subnav()">
15 15 ${self.repo_menu(active='showpullrequest')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 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 22 <div class="box pr-summary">
23 23
24 24 <div class="summary-details block-left">
25 25
26 26
27 27 <div class="pr-details-title">
28 28 ${_('New pull request')}
29 29 </div>
30 30
31 31 <div class="form" style="padding-top: 10px">
32 32 <!-- fields -->
33 33
34 34 <div class="fields" >
35 35
36 36 <div class="field">
37 37 <div class="label">
38 38 <label for="pullrequest_title">${_('Title')}:</label>
39 39 </div>
40 40 <div class="input">
41 41 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
42 42 </div>
43 43 <p class="help-block">
44 44 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
45 45 </p>
46 46 </div>
47 47
48 48 <div class="field">
49 49 <div class="label label-textarea">
50 50 <label for="pullrequest_desc">${_('Description')}:</label>
51 51 </div>
52 52 <div class="textarea text-area">
53 53 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
54 54 ${dt.markup_form('pullrequest_desc')}
55 55 </div>
56 56 </div>
57 57
58 58 <div class="field">
59 59 <div class="label label-textarea">
60 60 <label for="commit_flow">${_('Commit flow')}:</label>
61 61 </div>
62 62
63 63 ## TODO: johbo: Abusing the "content" class here to get the
64 64 ## desired effect. Should be replaced by a proper solution.
65 65
66 66 ##ORG
67 67 <div class="content">
68 68 <strong>${_('Source repository')}:</strong>
69 69 ${c.rhodecode_db_repo.description}
70 70 </div>
71 71 <div class="content">
72 72 ${h.hidden('source_repo')}
73 73 ${h.hidden('source_ref')}
74 74 </div>
75 75
76 76 ##OTHER, most Probably the PARENT OF THIS FORK
77 77 <div class="content">
78 78 ## filled with JS
79 79 <div id="target_repo_desc"></div>
80 80 </div>
81 81
82 82 <div class="content">
83 83 ${h.hidden('target_repo')}
84 84 ${h.hidden('target_ref')}
85 85 <span id="target_ref_loading" style="display: none">
86 86 ${_('Loading refs...')}
87 87 </span>
88 88 </div>
89 89 </div>
90 90
91 91 <div class="field">
92 92 <div class="label label-textarea">
93 93 <label for="pullrequest_submit"></label>
94 94 </div>
95 95 <div class="input">
96 96 <div class="pr-submit-button">
97 97 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
98 98 </div>
99 99 <div id="pr_open_message"></div>
100 100 </div>
101 101 </div>
102 102
103 103 <div class="pr-spacing-container"></div>
104 104 </div>
105 105 </div>
106 106 </div>
107 107 <div>
108 108 ## AUTHOR
109 109 <div class="reviewers-title block-right">
110 110 <div class="pr-details-title">
111 111 ${_('Author of this pull request')}
112 112 </div>
113 113 </div>
114 114 <div class="block-right pr-details-content reviewers">
115 115 <ul class="group_members">
116 116 <li>
117 117 ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)}
118 118 </li>
119 119 </ul>
120 120 </div>
121 121
122 122 ## REVIEW RULES
123 123 <div id="review_rules" style="display: none" class="reviewers-title block-right">
124 124 <div class="pr-details-title">
125 125 ${_('Reviewer rules')}
126 126 </div>
127 127 <div class="pr-reviewer-rules">
128 128 ## review rules will be appended here, by default reviewers logic
129 129 </div>
130 130 </div>
131 131
132 132 ## REVIEWERS
133 133 <div class="reviewers-title block-right">
134 134 <div class="pr-details-title">
135 135 ${_('Pull request reviewers')}
136 136 <span class="calculate-reviewers"> - ${_('loading...')}</span>
137 137 </div>
138 138 </div>
139 139 <div id="reviewers" class="block-right pr-details-content reviewers">
140 140 ## members goes here, filled via JS based on initial selection !
141 141 <input type="hidden" name="__start__" value="review_members:sequence">
142 142 <ul id="review_members" class="group_members"></ul>
143 143 <input type="hidden" name="__end__" value="review_members:sequence">
144 144 <div id="add_reviewer_input" class='ac'>
145 145 <div class="reviewer_ac">
146 146 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
147 147 <div id="reviewers_container"></div>
148 148 </div>
149 149 </div>
150 150 </div>
151 151 </div>
152 152 </div>
153 153 <div class="box">
154 154 <div>
155 155 ## overview pulled by ajax
156 156 <div id="pull_request_overview"></div>
157 157 </div>
158 158 </div>
159 159 ${h.end_form()}
160 160 </div>
161 161
162 162 <script type="text/javascript">
163 163 $(function(){
164 164 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
165 165 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
166 166 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
167 167 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
168 168
169 169 var $pullRequestForm = $('#pull_request_form');
170 170 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
171 171 var $sourceRepo = $('#source_repo', $pullRequestForm);
172 172 var $targetRepo = $('#target_repo', $pullRequestForm);
173 173 var $sourceRef = $('#source_ref', $pullRequestForm);
174 174 var $targetRef = $('#target_ref', $pullRequestForm);
175 175
176 176 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
177 177 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
178 178
179 179 var targetRepo = function() { return $targetRepo.eq(0).val() };
180 180 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
181 181
182 182 var calculateContainerWidth = function() {
183 183 var maxWidth = 0;
184 184 var repoSelect2Containers = ['#source_repo', '#target_repo'];
185 185 $.each(repoSelect2Containers, function(idx, value) {
186 186 $(value).select2('container').width('auto');
187 187 var curWidth = $(value).select2('container').width();
188 188 if (maxWidth <= curWidth) {
189 189 maxWidth = curWidth;
190 190 }
191 191 $.each(repoSelect2Containers, function(idx, value) {
192 192 $(value).select2('container').width(maxWidth + 10);
193 193 });
194 194 });
195 195 };
196 196
197 197 var initRefSelection = function(selectedRef) {
198 198 return function(element, callback) {
199 199 // translate our select2 id into a text, it's a mapping to show
200 200 // simple label when selecting by internal ID.
201 201 var id, refData;
202 202 if (selectedRef === undefined || selectedRef === null) {
203 203 id = element.val();
204 204 refData = element.val().split(':');
205 205
206 206 if (refData.length !== 3){
207 207 refData = ["", "", ""]
208 208 }
209 209 } else {
210 210 id = selectedRef;
211 211 refData = selectedRef.split(':');
212 212 }
213 213
214 214 var text = refData[1];
215 215 if (refData[0] === 'rev') {
216 216 text = text.substring(0, 12);
217 217 }
218 218
219 219 var data = {id: id, text: text};
220 220 callback(data);
221 221 };
222 222 };
223 223
224 224 var formatRefSelection = function(data, container, escapeMarkup) {
225 225 var prefix = '';
226 226 var refData = data.id.split(':');
227 227 if (refData[0] === 'branch') {
228 228 prefix = '<i class="icon-branch"></i>';
229 229 }
230 230 else if (refData[0] === 'book') {
231 231 prefix = '<i class="icon-bookmark"></i>';
232 232 }
233 233 else if (refData[0] === 'tag') {
234 234 prefix = '<i class="icon-tag"></i>';
235 235 }
236 236
237 237 var originalOption = data.element;
238 238 return prefix + escapeMarkup(data.text);
239 239 };formatSelection:
240 240
241 241 // custom code mirror
242 242 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
243 243
244 244 reviewersController = new ReviewersController();
245 245
246 246 var queryTargetRepo = function(self, query) {
247 247 // cache ALL results if query is empty
248 248 var cacheKey = query.term || '__';
249 249 var cachedData = self.cachedDataSource[cacheKey];
250 250
251 251 if (cachedData) {
252 252 query.callback({results: cachedData.results});
253 253 } else {
254 254 $.ajax({
255 255 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
256 256 data: {query: query.term},
257 257 dataType: 'json',
258 258 type: 'GET',
259 259 success: function(data) {
260 260 self.cachedDataSource[cacheKey] = data;
261 261 query.callback({results: data.results});
262 262 },
263 263 error: function(data, textStatus, errorThrown) {
264 264 alert(
265 265 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
266 266 }
267 267 });
268 268 }
269 269 };
270 270
271 271 var queryTargetRefs = function(initialData, query) {
272 272 var data = {results: []};
273 273 // filter initialData
274 274 $.each(initialData, function() {
275 275 var section = this.text;
276 276 var children = [];
277 277 $.each(this.children, function() {
278 278 if (query.term.length === 0 ||
279 279 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
280 280 children.push({'id': this.id, 'text': this.text})
281 281 }
282 282 });
283 283 data.results.push({'text': section, 'children': children})
284 284 });
285 285 query.callback({results: data.results});
286 286 };
287 287
288 288 var loadRepoRefDiffPreview = function() {
289 289
290 290 var url_data = {
291 291 'repo_name': targetRepo(),
292 292 'target_repo': sourceRepo(),
293 293 'source_ref': targetRef()[2],
294 294 'source_ref_type': 'rev',
295 295 'target_ref': sourceRef()[2],
296 296 'target_ref_type': 'rev',
297 297 'merge': true,
298 298 '_': Date.now() // bypass browser caching
299 299 }; // gather the source/target ref and repo here
300 300
301 301 if (sourceRef().length !== 3 || targetRef().length !== 3) {
302 302 prButtonLock(true, "${_('Please select source and target')}");
303 303 return;
304 304 }
305 305 var url = pyroutes.url('repo_compare', url_data);
306 306
307 307 // lock PR button, so we cannot send PR before it's calculated
308 308 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
309 309
310 310 if (loadRepoRefDiffPreview._currentRequest) {
311 311 loadRepoRefDiffPreview._currentRequest.abort();
312 312 }
313 313
314 314 loadRepoRefDiffPreview._currentRequest = $.get(url)
315 315 .error(function(data, textStatus, errorThrown) {
316 316 if (textStatus !== 'abort') {
317 317 alert(
318 318 "Error while processing request.\nError code {0} ({1}).".format(
319 319 data.status, data.statusText));
320 320 }
321 321
322 322 })
323 323 .done(function(data) {
324 324 loadRepoRefDiffPreview._currentRequest = null;
325 325 $('#pull_request_overview').html(data);
326 326
327 327 var commitElements = $(data).find('tr[commit_id]');
328 328
329 329 var prTitleAndDesc = getTitleAndDescription(
330 330 sourceRef()[1], commitElements, 5);
331 331
332 332 var title = prTitleAndDesc[0];
333 333 var proposedDescription = prTitleAndDesc[1];
334 334
335 335 var useGeneratedTitle = (
336 336 $('#pullrequest_title').hasClass('autogenerated-title') ||
337 337 $('#pullrequest_title').val() === "");
338 338
339 339 if (title && useGeneratedTitle) {
340 340 // use generated title if we haven't specified our own
341 341 $('#pullrequest_title').val(title);
342 342 $('#pullrequest_title').addClass('autogenerated-title');
343 343
344 344 }
345 345
346 346 var useGeneratedDescription = (
347 347 !codeMirrorInstance._userDefinedValue ||
348 348 codeMirrorInstance.getValue() === "");
349 349
350 350 if (proposedDescription && useGeneratedDescription) {
351 351 // set proposed content, if we haven't defined our own,
352 352 // or we don't have description written
353 353 codeMirrorInstance._userDefinedValue = false; // reset state
354 354 codeMirrorInstance.setValue(proposedDescription);
355 355 }
356 356
357 357 // refresh our codeMirror so events kicks in and it's change aware
358 358 codeMirrorInstance.refresh();
359 359
360 360 var msg = '';
361 361 if (commitElements.length === 1) {
362 362 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
363 363 } else {
364 364 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
365 365 }
366 366
367 367 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
368 368
369 369 if (commitElements.length) {
370 370 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
371 371 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
372 372 }
373 373 else {
374 374 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
375 375 }
376 376
377 377
378 378 });
379 379 };
380 380
381 381 var Select2Box = function(element, overrides) {
382 382 var globalDefaults = {
383 383 dropdownAutoWidth: true,
384 384 containerCssClass: "drop-menu",
385 385 dropdownCssClass: "drop-menu-dropdown"
386 386 };
387 387
388 388 var initSelect2 = function(defaultOptions) {
389 389 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
390 390 element.select2(options);
391 391 };
392 392
393 393 return {
394 394 initRef: function() {
395 395 var defaultOptions = {
396 396 minimumResultsForSearch: 5,
397 397 formatSelection: formatRefSelection
398 398 };
399 399
400 400 initSelect2(defaultOptions);
401 401 },
402 402
403 403 initRepo: function(defaultValue, readOnly) {
404 404 var defaultOptions = {
405 405 initSelection : function (element, callback) {
406 406 var data = {id: defaultValue, text: defaultValue};
407 407 callback(data);
408 408 }
409 409 };
410 410
411 411 initSelect2(defaultOptions);
412 412
413 413 element.select2('val', defaultSourceRepo);
414 414 if (readOnly === true) {
415 415 element.select2('readonly', true);
416 416 }
417 417 }
418 418 };
419 419 };
420 420
421 421 var initTargetRefs = function(refsData, selectedRef) {
422 422
423 423 Select2Box($targetRef, {
424 424 placeholder: "${_('Select commit reference')}",
425 425 query: function(query) {
426 426 queryTargetRefs(refsData, query);
427 427 },
428 428 initSelection : initRefSelection(selectedRef)
429 429 }).initRef();
430 430
431 431 if (!(selectedRef === undefined)) {
432 432 $targetRef.select2('val', selectedRef);
433 433 }
434 434 };
435 435
436 436 var targetRepoChanged = function(repoData) {
437 437 // generate new DESC of target repo displayed next to select
438 438 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
439 439 $('#target_repo_desc').html(
440 440 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
441 441 );
442 442
443 443 // generate dynamic select2 for refs.
444 444 initTargetRefs(repoData['refs']['select2_refs'],
445 445 repoData['refs']['selected_ref']);
446 446
447 447 };
448 448
449 449 var sourceRefSelect2 = Select2Box($sourceRef, {
450 450 placeholder: "${_('Select commit reference')}",
451 451 query: function(query) {
452 452 var initialData = defaultSourceRepoData['refs']['select2_refs'];
453 453 queryTargetRefs(initialData, query)
454 454 },
455 455 initSelection: initRefSelection()
456 456 }
457 457 );
458 458
459 459 var sourceRepoSelect2 = Select2Box($sourceRepo, {
460 460 query: function(query) {}
461 461 });
462 462
463 463 var targetRepoSelect2 = Select2Box($targetRepo, {
464 464 cachedDataSource: {},
465 465 query: $.debounce(250, function(query) {
466 466 queryTargetRepo(this, query);
467 467 }),
468 468 formatResult: formatRepoResult
469 469 });
470 470
471 471 sourceRefSelect2.initRef();
472 472
473 473 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
474 474
475 475 targetRepoSelect2.initRepo(defaultTargetRepo, false);
476 476
477 477 $sourceRef.on('change', function(e){
478 478 loadRepoRefDiffPreview();
479 479 reviewersController.loadDefaultReviewers(
480 480 sourceRepo(), sourceRef(), targetRepo(), targetRef());
481 481 });
482 482
483 483 $targetRef.on('change', function(e){
484 484 loadRepoRefDiffPreview();
485 485 reviewersController.loadDefaultReviewers(
486 486 sourceRepo(), sourceRef(), targetRepo(), targetRef());
487 487 });
488 488
489 489 $targetRepo.on('change', function(e){
490 490 var repoName = $(this).val();
491 491 calculateContainerWidth();
492 492 $targetRef.select2('destroy');
493 493 $('#target_ref_loading').show();
494 494
495 495 $.ajax({
496 496 url: pyroutes.url('pullrequest_repo_refs',
497 497 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
498 498 data: {},
499 499 dataType: 'json',
500 500 type: 'GET',
501 501 success: function(data) {
502 502 $('#target_ref_loading').hide();
503 503 targetRepoChanged(data);
504 504 loadRepoRefDiffPreview();
505 505 },
506 506 error: function(data, textStatus, errorThrown) {
507 507 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
508 508 }
509 509 })
510 510
511 511 });
512 512
513 513 $pullRequestForm.on('submit', function(e){
514 514 // Flush changes into textarea
515 515 codeMirrorInstance.save();
516 516 prButtonLock(true, null, 'all');
517 $pullRequestSubmit.val(_gettext('Please wait creating pull request...'));
517 518 });
518 519
519 520 prButtonLock(true, "${_('Please select source and target')}", 'all');
520 521
521 522 // auto-load on init, the target refs select2
522 523 calculateContainerWidth();
523 524 targetRepoChanged(defaultTargetRepoData);
524 525
525 526 $('#pullrequest_title').on('keyup', function(e){
526 527 $(this).removeClass('autogenerated-title');
527 528 });
528 529
529 530 % if c.default_source_ref:
530 531 // in case we have a pre-selected value, use it now
531 532 $sourceRef.select2('val', '${c.default_source_ref}');
532 533 // diff preview load
533 534 loadRepoRefDiffPreview();
534 535 // default reviewers
535 536 reviewersController.loadDefaultReviewers(
536 537 sourceRepo(), sourceRef(), targetRepo(), targetRef());
537 538 % endif
538 539
539 540 ReviewerAutoComplete('#user');
540 541 });
541 542 </script>
542 543
543 544 </%def>
General Comments 0
You need to be logged in to leave comments. Login now