OLD | NEW |
| (Empty) |
1 {% extends "layout.html" %} | |
2 | |
3 {% block head %} | |
4 {{ super() }} | |
5 <script type='text/javascript'> | |
6 // <![CDATA[ | |
7 // | |
8 | |
9 // Data loaded from the master's JSON interface. | |
10 var builders = {}; | |
11 var revisions = []; | |
12 | |
13 // Number of events to load from the master. | |
14 var console_limit = {{ default_console_limit }}; | |
15 var default_console_limit = {{ default_console_limit }}; | |
16 var max_console_limit = {{ max_console_limit }}; | |
17 | |
18 // Minimum number of revisions to display on the console. | |
19 var min_display_revisions = 25; | |
20 | |
21 // | |
22 // Make the commit message display more or less. | |
23 // | |
24 function toggleMore(id, visible) { | |
25 if (visible) { | |
26 document.getElementById(id + "_long_desc").style.display = "inline"; | |
27 document.getElementById(id + "_morelink").style.display = "none"; | |
28 document.getElementById(id + "_lesslink").style.display = "inline"; | |
29 } else { | |
30 document.getElementById(id + "_long_desc").style.display = "none"; | |
31 document.getElementById(id + "_morelink").style.display = "inline"; | |
32 document.getElementById(id + "_lesslink").style.display = "none"; | |
33 } | |
34 } | |
35 | |
36 // | |
37 // Functions used to display the build status bubble on box click. | |
38 // | |
39 | |
40 // show the build status box. This is called when the user clicks on a block. | |
41 function showBuildBox(url, event) { | |
42 // Find the current curson position. | |
43 var cursorPosTop = (window.event ? window.event.clientY : event.pageY) | |
44 var cursorPosLeft = (window.event ? window.event.clientX : event.pageX) | |
45 | |
46 // Offset the position by 5, to make the window appears under the cursor. | |
47 cursorPosTop = cursorPosTop + document.body.scrollTop -5 ; | |
48 cursorPosLeft = cursorPosLeft + document.body.scrollLeft - 5; | |
49 | |
50 // Move the div (hidden) under the cursor. | |
51 var divBox = document.getElementById('divBox'); | |
52 divBox.style.top = parseInt(cursorPosTop) + 'px'; | |
53 divBox.style.left = parseInt(cursorPosLeft) + 'px'; | |
54 | |
55 // Reload the hidden frame with the build page we want to show. | |
56 // The onload even on this frame will update the div and make it visible. | |
57 document.getElementById("frameBox").src = url | |
58 | |
59 // We don't want to reload the page. | |
60 return false; | |
61 } | |
62 | |
63 // OnLoad handler for the iframe containing the build to show. | |
64 function updateDiv(event) { | |
65 // Get the frame innerHTML. | |
66 var iframeContent = document.getElementById("frameBox").contentWindow.docume
nt.body.innerHTML; | |
67 | |
68 // If there is any content, update the div, and make it visible. | |
69 if (iframeContent) { | |
70 var divBox = document.getElementById('divBox'); | |
71 divBox.innerHTML = iframeContent ; | |
72 divBox.style.display = "block"; | |
73 } | |
74 } | |
75 | |
76 // Util functions to know if an element is contained inside another element. | |
77 // We use this to know when we mouse out our build status div. | |
78 function containsDOM (container, containee) { | |
79 var isParent = false; | |
80 do { | |
81 if ((isParent = container == containee)) | |
82 break; | |
83 containee = containee.parentNode; | |
84 } while (containee != null); | |
85 | |
86 return isParent; | |
87 } | |
88 | |
89 // OnMouseOut handler. Returns true if the mouse moved out of the element. | |
90 // It is false if the mouse is still in the element, but in a blank part of it, | |
91 // like in an empty table cell. | |
92 function checkMouseLeave(element, event) { | |
93 if (element.contains && event.toElement) { | |
94 return !element.contains(event.toElement); | |
95 } | |
96 else if (event.relatedTarget) { | |
97 return !containsDOM(element, event.relatedTarget); | |
98 } | |
99 } | |
100 | |
101 // Obtain the current date and time in a nicely formatted string. | |
102 function getDateString() { | |
103 var date = new Date(); | |
104 return date.toDateString() + " at " + date.toTimeString(); | |
105 } | |
106 | |
107 // Set the "loading" message. | |
108 function setLoadingMessage(msg) { | |
109 var loading = document.getElementById("loading_div"); | |
110 if (loading) { | |
111 loading.innerHTML = msg; | |
112 } | |
113 } | |
114 | |
115 // Load the console page data. | |
116 function loadConsoleData() { | |
117 // Note: We don't include "&revs=" here because that would artificially limit | |
118 // the number of revisions we get back and therefore prevent us from being | |
119 // able to scale back console_limit when it no longer needs to be very big. | |
120 loadJSONP('/console_json?limit=' + console_limit, fixConsoleData); | |
121 setLoadingMessage("Loading..."); | |
122 } | |
123 | |
124 // Sort the categories, subcategories, and builders for display. | |
125 function fixConsoleData(data) { | |
126 if (data["revisions"].length < min_display_revisions && | |
127 console_limit < max_console_limit) { | |
128 console_limit = Math.min(console_limit * 2, max_console_limit); | |
129 loadConsoleData(); | |
130 return; | |
131 } | |
132 if (data["revisions"].length > min_display_revisions * 2) { | |
133 console_limit = Math.max(console_limit / 1.5, default_console_limit); | |
134 } | |
135 var new_builders = {}; | |
136 var categories = Object.keys(data["builders"]); | |
137 categories.sort(); | |
138 for (var category_index in categories) { | |
139 var category = categories[category_index]; | |
140 new_builders[category] = {}; | |
141 var subcategories = Object.keys(data["builders"][category]); | |
142 subcategories.sort() | |
143 for (var subcategory_index in subcategories) { | |
144 var subcategory = subcategories[subcategory_index]; | |
145 new_builders[category][subcategory] = data["builders"][category][subcatego
ry]; | |
146 for (var cat_full in new_builders[category][subcategory]) { | |
147 new_builders[category][subcategory][cat_full].sort(); | |
148 } | |
149 } | |
150 } | |
151 builders = new_builders; | |
152 revisions = data["revisions"]; | |
153 setLoadingMessage("Last loaded at " + getDateString()); | |
154 buildConsoleTable(); | |
155 } | |
156 | |
157 // Create a new row with two spacer cells. | |
158 function newRowWithSpacers(table) { | |
159 var row = table.insertRow(-1); | |
160 var spacer = row.insertCell(-1) | |
161 spacer.style.width = "1%"; | |
162 var spacer = row.insertCell(-1) | |
163 spacer.style.width = "1%"; | |
164 return row; | |
165 } | |
166 | |
167 function newHyperlink(href, text, open_in_new_tab) { | |
168 var link = document.createElement("a"); | |
169 link.href = href; | |
170 if (text) { | |
171 link.innerHTML = text; | |
172 } | |
173 if (open_in_new_tab == true) { | |
174 link.target = "_blank"; | |
175 } | |
176 return link; | |
177 } | |
178 | |
179 // Build the main console view table. | |
180 function buildConsoleTable() { | |
181 // The main console table. | |
182 var table = document.getElementById("console_table"); | |
183 | |
184 // Clear the table. | |
185 while (table.hasChildNodes()) { | |
186 table.removeChild(table.lastChild); | |
187 } | |
188 | |
189 // Count the number of columns, starting with 2 for the spacer TDs, so that | |
190 // we can get the colspans right. | |
191 var total_cols = 2; | |
192 | |
193 // Top row: Categories. | |
194 var top_row = newRowWithSpacers(table); | |
195 | |
196 // Second row: Subcategories. | |
197 var second_row = newRowWithSpacers(table); | |
198 | |
199 // Third row: Individual builder statuses. | |
200 var builder_row = newRowWithSpacers(table); | |
201 | |
202 // Keep track of the first and last visible categories. | |
203 var first_category = null; | |
204 var last_category = null; | |
205 | |
206 // This loop creates the content for the top three rows. | |
207 for (var category in builders) { | |
208 // Create the category headers with links to the waterfall for each. | |
209 var category_td = top_row.insertCell(-1); | |
210 category_td.id = "category_header_" + category; | |
211 var td_class = "DevStatus Alt category_" + category; | |
212 category_td.className = td_class; | |
213 var category_link = newHyperlink("/waterfall?"); | |
214 var h3 = document.createElement("h3"); | |
215 h3.innerHTML = category; | |
216 // Hyperlink to the waterfall page which displays all builders within this | |
217 // category. Each full category name is appended in the subsequent loops. | |
218 category_link.appendChild(h3); | |
219 category_td.appendChild(category_link); | |
220 | |
221 if (!allCategories[category]) { | |
222 // Hide the column header for this category if the associated checkbox | |
223 // isn't checked. | |
224 category_td.style.display = "none"; | |
225 } else { | |
226 // If the category is visible, mark it as a possible first or last visible | |
227 // category. | |
228 if (!first_category) { | |
229 first_category = category; | |
230 } | |
231 last_category = category; | |
232 } | |
233 | |
234 // Keep track of the colspan (number of builders) for this category as we | |
235 // loop over the subcategories. | |
236 var category_colspan = 0; | |
237 | |
238 // Loop over the subcategories. | |
239 for (var subcategory in builders[category]) { | |
240 // Create the subcategory headers with links to the waterfall for each. | |
241 var subcategory_td = second_row.insertCell(-1); | |
242 subcategory_td.className = "DevStatus category_" + category + "_subcategor
y_" + subcategory; | |
243 subcategory_td.colSpan = builders[category][subcategory].length; | |
244 // Hyperlink to the waterfall page which displays all builders within this | |
245 // subcategory. Each full category name is appended in the subsequent | |
246 // loops. | |
247 var subcategory_link = newHyperlink("/waterfall?", subcategory) | |
248 subcategory_td.appendChild(subcategory_link); | |
249 | |
250 // Hide the column header for this subcategory if the associated checkbox | |
251 // isn't checked. | |
252 if (!(allCategories[category] && allSubcategories[subcategory])) { | |
253 subcategory_td.style.display = "none"; | |
254 } | |
255 | |
256 // Loop over the full category names (eg. "Build|Ubuntu12|GateKeeper") | |
257 // within each subcategory. | |
258 for (var cat_full in builders[category][subcategory]) { | |
259 // Create a wrapper cell to hold the builder statuses for this category. | |
260 var builder_wrapper_td = builder_row.insertCell(-1); | |
261 builder_wrapper_td.className = "DevSlave Alt category_" + category + "_s
ubcategory_" + subcategory; | |
262 // Create a sub-table within the builder wrapper cell. | |
263 var builder_wrapper_table = document.createElement("table"); | |
264 builder_wrapper_table.width = "100%"; | |
265 builder_wrapper_td.appendChild(builder_wrapper_table); | |
266 var builder_wrapper_table_tr = builder_wrapper_table.insertRow(-1); | |
267 // Hide the builder wrapper cell if the current category and subcategory | |
268 // aren't both displaying. | |
269 if (!(allCategories[category] && allSubcategories[subcategory])) { | |
270 builder_wrapper_td.style.display = "none"; | |
271 } | |
272 | |
273 // Append the full category name to the category and subcategory | |
274 // waterfall links. | |
275 if (category_link.href.indexOf("?", category_link.href.length - 1) == -1
) { | |
276 category_link.href += "&"; | |
277 } | |
278 category_link.href += "category=" + cat_full; | |
279 if (subcategory_link.href.indexOf("?", subcategory_link.href.length - 1)
== -1) { | |
280 subcategory_link.href += "&"; | |
281 } | |
282 subcategory_link.href += "category=" + cat_full; | |
283 | |
284 // Loop over the builders within this fully-qualified category. | |
285 for (var builder_index in builders[category][subcategory][cat_full]) { | |
286 // Create a colored cell with a link to this builder's status page. | |
287 var builder = builders[category][subcategory][cat_full][builder_index]
; | |
288 var builder_name = builder.builderName; | |
289 var builder_td = builder_wrapper_table_tr.insertCell(-1); | |
290 builder_td.className = "DevSlaveBox"; | |
291 builder_td.builder_name = builder_name; | |
292 builder_link = newHyperlink(builder.url, "", true); | |
293 builder_link.id = "commentIndicator_" + builder_name; | |
294 builder_link.className = "DevSlaveBox " + builder.color; | |
295 if (builder_statuses[builder_name]) { | |
296 builder_link.classList.add("BuilderHasComment"); | |
297 } | |
298 builder_td.appendChild(builder_link); | |
299 | |
300 // Create the status tooltip for this builder. | |
301 var tooltip_div = document.createElement("div"); | |
302 tooltip_div.className = "BuilderTooltip RoundedRect"; | |
303 buildComment(builder_name, tooltip_div); | |
304 builder_td.appendChild(tooltip_div); | |
305 } | |
306 // Only count the columns which are being displayed toward the total | |
307 // colspan. | |
308 if (allCategories[category] && allSubcategories[subcategory]) { | |
309 total_cols++; | |
310 } | |
311 } | |
312 // Only cound the subcategories which are being displayed toward the | |
313 // categories' colspans. | |
314 if (allCategories[category] && allSubcategories[subcategory]) { | |
315 category_colspan += subcategory_td.colSpan; | |
316 } | |
317 } | |
318 category_td.colSpan = category_colspan; | |
319 } | |
320 | |
321 // Round the corners of the first and last visible category headers. | |
322 if (first_category) { | |
323 document.getElementById("category_header_" + first_category).classList.add("
first"); | |
324 } | |
325 if (last_category) { | |
326 document.getElementById("category_header_" + last_category).classList.add("l
ast") | |
327 } | |
328 | |
329 // Build rows for each revision. | |
330 for (var rev_index in revisions) { | |
331 // Information about this revision. | |
332 var revision = revisions[rev_index]; | |
333 | |
334 // String which alternates between "" and " Alt"; used for creating | |
335 // alternating shades of gray. | |
336 var alt = ""; | |
337 if (rev_index % 2) { | |
338 alt = " Alt"; | |
339 } | |
340 | |
341 // Row containing the commit hash, committer, and statuses for each builder | |
342 // at this revision. | |
343 var rev_status_tr = table.insertRow(-1); | |
344 // Cell containing the commit hash with a link to the commit in the | |
345 // repository. | |
346 var revision_td = rev_status_tr.insertCell(-1); | |
347 revision_td.className = "DevRev" + alt; | |
348 var revision_link = newHyperlink(revision.repository + "/+/" + revision.id, | |
349 revision.id.substring(0, 11), | |
350 true); | |
351 revision_td.appendChild(revision_link); | |
352 // Cell containing the committer's username. | |
353 var dev_name_td = rev_status_tr.insertCell(-1); | |
354 dev_name_td.className = "DevName" + alt; | |
355 dev_name_td.width = "1%"; | |
356 dev_name_td.innerHTML = revision.who; | |
357 | |
358 // Loop over the builders, obtaining the status and creating a build status | |
359 // popup link for each. | |
360 for (var category in builders) { | |
361 for (var subcategory in builders[category]) { | |
362 for (var cat_full in builders[category][subcategory]) { | |
363 // Wrapper cell containing statuses for builders in this fully- | |
364 // qualified category. | |
365 var build_status_td = rev_status_tr.insertCell(-1); | |
366 // Hide the build status cell if we're not displaying both the | |
367 // builder's category and subcategory. | |
368 if (!allCategories[category] || !allSubcategories[subcategory]) { | |
369 build_status_td.style.display = "none"; | |
370 } | |
371 build_status_td.className = "DevStatus category_" + category + "_subca
tegory_" + subcategory + alt; | |
372 // Table which will hold the individual builder statuses. | |
373 var build_status_wrapper = document.createElement("table"); | |
374 build_status_wrapper.width = "100%"; | |
375 build_status_td.appendChild(build_status_wrapper); | |
376 var builder_wrapper_tr = build_status_wrapper.insertRow(-1); | |
377 // Loop over the builders in this fully-qualified category. | |
378 for (var builder_index in builders[category][subcategory][cat_full]) { | |
379 var builder = builders[category][subcategory][cat_full][builder_inde
x]; | |
380 // Loop over the list of builds at this revision for this builder. | |
381 // This is almost always a single build. | |
382 for (var build_index in revision.builds[builder.builderName]) { | |
383 var build = revision.builds[builder.builderName][build_index]; | |
384 // Create a colored cell representing the result of this build, | |
385 // with a link to open a popup with more detailed build step | |
386 // statuses. | |
387 var build_td = builder_wrapper_tr.insertCell(-1); | |
388 build_td.className = "DevStatusBox"; | |
389 var build_link = newHyperlink("#", "", true); | |
390 build_link.popup_url = build.url; | |
391 build_link.onclick = function(event) { showBuildBox(this.popup_url
, event); return false; }; | |
392 build_link.title = build.pageTitle; | |
393 build_link.className = "DevStatusBox " + build.color + " " + build
.tag; | |
394 build_td.appendChild(build_link); | |
395 } | |
396 } | |
397 } | |
398 } | |
399 } | |
400 | |
401 // Row containing the change description for this revision. | |
402 var rev_comment_tr = table.insertRow(-1); | |
403 var rev_comment_td = rev_comment_tr.insertCell(-1); | |
404 rev_comment_td.className = "DevComment" + alt; | |
405 rev_comment_td.colSpan = total_cols; | |
406 // Split the comment text into: first line (short) and remainder (long). | |
407 var comment_short_text_span = document.createElement("span"); | |
408 rev_comment_td.appendChild(comment_short_text_span); | |
409 var comment_long_text_span = document.createElement("span"); | |
410 comment_long_text_span.id = revision.id + "_long_desc"; | |
411 comment_long_text_span.style.display = "none"; | |
412 // Create "more" and "less" links. | |
413 var more_link = document.createElement("a"); | |
414 more_link.href = "javascript:toggleMore('" + revision.id + "', true)"; | |
415 more_link.id = revision.id + "_morelink"; | |
416 more_link.innerHTML = "more"; | |
417 rev_comment_td.appendChild(more_link); | |
418 var less_link = document.createElement("a"); | |
419 less_link.href = "javascript:toggleMore('" + revision.id + "', false)"; | |
420 less_link.id = revision.id + "_lesslink"; | |
421 less_link.style.display = "none"; | |
422 less_link.innerHTML = "less"; | |
423 rev_comment_td.appendChild(less_link); | |
424 rev_comment_td.appendChild(comment_long_text_span); | |
425 var comment_text_lines = revision.comments.split("\n"); | |
426 // Only include the "more" link if the comment has more than one line. | |
427 if (comment_text_lines.length > 1) { | |
428 comment_short_text_span.innerHTML = comment_text_lines[0] + " ... "; | |
429 var comment_long_text_para = document.createElement("p"); | |
430 comment_long_text_span.appendChild(comment_long_text_para); | |
431 for (var comment_line = 1; comment_line < comment_text_lines.length; ++com
ment_line) { | |
432 comment_long_text_span.innerHTML += comment_text_lines[comment_line] + "
<br/>"; | |
433 } | |
434 comment_long_text_span.innerHTML = addLinks(comment_long_text_span.innerHT
ML); | |
435 } else { | |
436 comment_short_text_span.innerHTML = revision.comments; | |
437 more_link.style.display = "none"; | |
438 } | |
439 | |
440 // Optional row containing more information about the revision, for example | |
441 // test failures. | |
442 if (revision["details"] && revision["details"].length > 0) { | |
443 var details_tr = table.insertRow(-1); | |
444 var details_td = details_tr.insertCell(-1); | |
445 details_td.colSpan = total_cols; | |
446 details_td.className = "DevDetails bottom" + alt; | |
447 var details_list = document.createElement("ul"); | |
448 for (var detail_index in revision["details"]) { | |
449 var detail = revision["details"][detail_index]; | |
450 var detail_item = document.createElement("li"); | |
451 var detail_text = document.createElement("span"); | |
452 detail_text.innerHTML = detail["buildername"] + ": " + detail["status"]
+ " - "; | |
453 detail_item.appendChild(detail_text); | |
454 for (var log_index in detail["logs"]) { | |
455 var log = detail["logs"][log_index]; | |
456 var log_link = document.createElement("a"); | |
457 log_link.href = log["url"]; | |
458 log_link.innerHTML = log["name"]; | |
459 detail_item.appendChild(log_link); | |
460 } | |
461 details_list.appendChild(detail_item); | |
462 } | |
463 details_td.appendChild(details_list); | |
464 } else { | |
465 rev_comment_td.className += " bottom"; | |
466 } | |
467 | |
468 // Spacing row just containing whitespace. | |
469 var rev_spacing_tr = table.insertRow(-1); | |
470 rev_spacing_tr.className = "DevStatusSpacing"; | |
471 var rev_spacing_td = rev_spacing_tr.insertCell(-1); | |
472 } | |
473 } | |
474 | |
475 // ]]> | |
476 </script> | |
477 {% endblock %} | |
478 | |
479 {% block content %} | |
480 | |
481 <h1>Console View</h1> | |
482 | |
483 <div id="loading_div"></div> | |
484 | |
485 <div align="center"> | |
486 <table width="95%" class="Grid" border="0" cellspacing="0"> | |
487 <tr> | |
488 <td width="33%" align="left" class="left_align"> | |
489 {% if repository %} | |
490 <br><b>Repository:</b> {{ repository|e }} | |
491 {% endif %} | |
492 </td> | |
493 <td width="33%" align="center" class="center_align"> | |
494 <div align="center"> | |
495 <table class="info"> | |
496 <tr> | |
497 <td>Legend: </td> | |
498 <td class='legend success' title='All tests passed'>Passed</td> | |
499 <td class='legend failure' title='There is a new failure. Take a l
ook!'>Failed</td> | |
500 <td class='legend warnings' title='It was failing before, and it i
s still failing. Make sure you did not introduce new regressions'>Failed Ag
ain</td> | |
501 <td class='legend running' title='The tests are still running'>Run
ning</td> | |
502 <td class='legend running_failure' title='The tests are still runn
ing, but at least one step has failed.'>Running w/ failures</td> | |
503 <td class='legend exception' title='Something went wrong with the
test, there is no result'>Exception</td> | |
504 <td class='legend offline' title='The builder is offline, as there
are no slaves connected to it'>Offline</td> | |
505 <td class='legend notstarted' title='No result yet.'>No data<
/td> | |
506 <td class='legend BuilderHasComment' title='This builder has a com
ment.'>Builder comment</td> | |
507 </tr> | |
508 </table> | |
509 </div> | |
510 </td> | |
511 <td width="33%" align="right" class="right_align"> | |
512 <script type="text/javascript"> | |
513 // <![CDATA[ | |
514 function reload_page() { | |
515 name_value = document.getElementById('namebox').value | |
516 if (document.location.href.lastIndexOf('?') == -1) | |
517 document.location.href = document.location.href+ '?name=' + name_v
alue; | |
518 else | |
519 document.location.href = document.location.href+ '&name=' + name_v
alue; | |
520 } | |
521 // ]]> | |
522 </script> | |
523 <input id='namebox' name='name' type='text' style='color:#999;' | |
524 onblur='this.value = this.value || this.defaultValue; this.style.col
or = "#999";' | |
525 onfocus='this.value=""; this.style.color = "#000";' | |
526 value='Personalized for...'/> | |
527 <input type='submit' value='Go' onclick='reload_page()'/> | |
528 </td> | |
529 </tr> | |
530 </table> | |
531 </div> | |
532 | |
533 <br/> | |
534 <div align="center"> | |
535 <table id="console_table" width="96%"></table> | |
536 </div> | |
537 | |
538 | |
539 <div id="divBox" onmouseout="if (checkMouseLeave(this, event)) this.style.displa
y = 'none'" class="BuildWaterfall" style="display: none;"> | |
540 </div> | |
541 | |
542 | |
543 <iframe id="frameBox" style="display: none;"></iframe> | |
544 | |
545 <script type="text/javascript"> | |
546 // replace 'onload="updateDiv(event);" with this, as iframe doesn't have onload
event in xhtml | |
547 window.onload = function() { | |
548 document.getElementById('frameBox').onload = function(event) { | |
549 updateDiv(event); | |
550 }; | |
551 }; | |
552 </script> | |
553 | |
554 {% endblock %} | |
555 | |
556 | |
557 {% block footer %} | |
558 | |
559 {{ super() }} | |
560 {# <p>Debug info: {{ debuginfo }}</p> #} | |
561 {% endblock %} | |
OLD | NEW |