blob: a7aad7eee886b961b9dc310fd0af7318f2fb36d3 [file] [log] [blame]
{% extends "layout.html" %}
{% block head %}
{{ super() }}
<script type='text/javascript'>
// <![CDATA[
//
// Data loaded from the master's JSON interface.
var builders = {};
var revisions = [];
function addLinks(text) {
if (text && text.indexOf("<a href=") == -1) {
text = text.replace(
/(https?:\/\/.+?)(\s|\t|<|\n|\r|$)/g, '<a href="$1" target="_blank">$1</a>$2');
}
return text;
}
//
// Make the commit message display more or less.
//
function toggleMore(id, visible) {
if (visible) {
document.getElementById(id + "_long_desc").style.display = "inline";
document.getElementById(id + "_morelink").style.display = "none";
document.getElementById(id + "_lesslink").style.display = "inline";
} else {
document.getElementById(id + "_long_desc").style.display = "none";
document.getElementById(id + "_morelink").style.display = "inline";
document.getElementById(id + "_lesslink").style.display = "none";
}
}
//
// Functions used to display the build status bubble on box click.
//
// show the build status box. This is called when the user clicks on a block.
function showBuildBox(url, event) {
// Find the current curson position.
var cursorPosTop = (window.event ? window.event.clientY : event.pageY)
var cursorPosLeft = (window.event ? window.event.clientX : event.pageX)
// Offset the position by 5, to make the window appears under the cursor.
cursorPosTop = cursorPosTop + document.body.scrollTop -5 ;
cursorPosLeft = cursorPosLeft + document.body.scrollLeft - 5;
// Move the div (hidden) under the cursor.
var divBox = document.getElementById('divBox');
divBox.style.top = parseInt(cursorPosTop) + 'px';
divBox.style.left = parseInt(cursorPosLeft) + 'px';
// Reload the hidden frame with the build page we want to show.
// The onload even on this frame will update the div and make it visible.
document.getElementById("frameBox").src = url
// We don't want to reload the page.
return false;
}
// OnLoad handler for the iframe containing the build to show.
function updateDiv(event) {
// Get the frame innerHTML.
var iframeContent = document.getElementById("frameBox").contentWindow.document.body.innerHTML;
// If there is any content, update the div, and make it visible.
if (iframeContent) {
var divBox = document.getElementById('divBox');
divBox.innerHTML = iframeContent ;
divBox.style.display = "block";
}
}
// Util functions to know if an element is contained inside another element.
// We use this to know when we mouse out our build status div.
function containsDOM (container, containee) {
var isParent = false;
do {
if ((isParent = container == containee))
break;
containee = containee.parentNode;
} while (containee != null);
return isParent;
}
// OnMouseOut handler. Returns true if the mouse moved out of the element.
// It is false if the mouse is still in the element, but in a blank part of it,
// like in an empty table cell.
function checkMouseLeave(element, event) {
if (element.contains && event.toElement) {
return !element.contains(event.toElement);
}
else if (event.relatedTarget) {
return !containsDOM(element, event.relatedTarget);
}
}
// Obtain the current date and time in a nicely formatted string.
function getDateString() {
var date = new Date();
return date.toDateString() + " at " + date.toTimeString();
}
// Set the "loading" message.
function setLoadingMessage(msg) {
var loading = document.getElementById("loading_div");
if (loading) {
loading.innerHTML = msg;
}
}
// Load the console page data.
function loadConsoleData() {
loadJSONP('/console_json', fixConsoleData);
setLoadingMessage("Loading...");
}
// Sort the categories, subcategories, and builders for display.
function fixConsoleData(data) {
var new_builders = {};
var categories = Object.keys(data["builders"]);
categories.sort();
for (var category_index in categories) {
var category = categories[category_index];
new_builders[category] = {};
var subcategories = Object.keys(data["builders"][category]);
subcategories.sort()
for (var subcategory_index in subcategories) {
var subcategory = subcategories[subcategory_index];
new_builders[category][subcategory] = data["builders"][category][subcategory];
for (var cat_full in new_builders[category][subcategory]) {
new_builders[category][subcategory][cat_full].sort();
}
}
}
builders = new_builders;
revisions = data["revisions"];
setLoadingMessage("Last loaded at " + getDateString());
buildConsoleTable();
}
// Create a new row with two spacer cells.
function newRowWithSpacers(table) {
var row = table.insertRow(-1);
var spacer = row.insertCell(-1)
spacer.style.width = "1%";
var spacer = row.insertCell(-1)
spacer.style.width = "1%";
return row;
}
function newHyperlink(href, text, open_in_new_tab) {
var link = document.createElement("a");
link.href = href;
if (text) {
link.innerHTML = text;
}
if (open_in_new_tab == true) {
link.target = "_blank";
}
return link;
}
// Build the main console view table.
function buildConsoleTable() {
// The main console table.
var table = document.getElementById("console_table");
// Clear the table.
while (table.hasChildNodes()) {
table.removeChild(table.lastChild);
}
// Count the number of columns, starting with 2 for the spacer TDs, so that
// we can get the colspans right.
var total_cols = 2;
// Top row: Categories.
var top_row = newRowWithSpacers(table);
// Second row: Subcategories.
var second_row = newRowWithSpacers(table);
// Third row: Individual builder statuses.
var builder_row = newRowWithSpacers(table);
// Keep track of the first and last visible categories.
var first_category = null;
var last_category = null;
// This loop creates the content for the top three rows.
for (var category in builders) {
// Create the category headers with links to the waterfall for each.
var category_td = top_row.insertCell(-1);
category_td.id = "category_header_" + category;
var td_class = "DevStatus Alt category_" + category;
category_td.className = td_class;
var category_link = newHyperlink("/waterfall?");
var h3 = document.createElement("h3");
h3.innerHTML = category;
// Hyperlink to the waterfall page which displays all builders within this
// category. Each full category name is appended in the subsequent loops.
category_link.appendChild(h3);
category_td.appendChild(category_link);
if (!allCategories[category]) {
// Hide the column header for this category if the associated checkbox
// isn't checked.
category_td.style.display = "none";
} else {
// If the category is visible, mark it as a possible first or last visible
// category.
if (!first_category) {
first_category = category;
}
last_category = category;
}
// Keep track of the colspan (number of builders) for this category as we
// loop over the subcategories.
var category_colspan = 0;
// Loop over the subcategories.
for (var subcategory in builders[category]) {
// Create the subcategory headers with links to the waterfall for each.
var subcategory_td = second_row.insertCell(-1);
subcategory_td.className = "DevStatus category_" + category + "_subcategory_" + subcategory;
subcategory_td.colSpan = builders[category][subcategory].length;
// Hyperlink to the waterfall page which displays all builders within this
// subcategory. Each full category name is appended in the subsequent
// loops.
var subcategory_link = newHyperlink("/waterfall?", subcategory)
subcategory_td.appendChild(subcategory_link);
// Hide the column header for this subcategory if the associated checkbox
// isn't checked.
if (!(allCategories[category] && allSubcategories[subcategory])) {
subcategory_td.style.display = "none";
}
// Loop over the full category names (eg. "Build|Ubuntu12|GateKeeper")
// within each subcategory.
for (var cat_full in builders[category][subcategory]) {
// Create a wrapper cell to hold the builder statuses for this category.
var builder_wrapper_td = builder_row.insertCell(-1);
builder_wrapper_td.className = "DevSlave Alt category_" + category + "_subcategory_" + subcategory;
// Create a sub-table within the builder wrapper cell.
var builder_wrapper_table = document.createElement("table");
builder_wrapper_table.width = "100%";
builder_wrapper_td.appendChild(builder_wrapper_table);
var builder_wrapper_table_tr = builder_wrapper_table.insertRow(-1);
// Hide the builder wrapper cell if the current category and subcategory
// aren't both displaying.
if (!(allCategories[category] && allSubcategories[subcategory])) {
builder_wrapper_td.style.display = "none";
}
// Append the full category name to the category and subcategory
// waterfall links.
if (category_link.href.indexOf("?", category_link.href.length - 1) == -1) {
category_link.href += "&";
}
category_link.href += "category=" + cat_full;
if (subcategory_link.href.indexOf("?", subcategory_link.href.length - 1) == -1) {
subcategory_link.href += "&";
}
subcategory_link.href += "category=" + cat_full;
// Loop over the builders within this fully-qualified category.
for (var builder_index in builders[category][subcategory][cat_full]) {
// Create a colored cell with a link to this builder's status page.
var builder = builders[category][subcategory][cat_full][builder_index];
var builder_name = builder.builderName;
var builder_td = builder_wrapper_table_tr.insertCell(-1);
builder_td.className = "DevSlaveBox";
builder_td.builder_name = builder_name;
builder_link = newHyperlink(builder.url, "", true);
builder_link.id = "commentIndicator_" + builder_name;
builder_link.className = "DevSlaveBox " + builder.color;
if (builder_statuses[builder_name]) {
builder_link.classList.add("BuilderHasComment");
}
builder_td.appendChild(builder_link);
// Create the status tooltip for this builder.
var tooltip_div = document.createElement("div");
tooltip_div.className = "BuilderTooltip RoundedRect";
tooltip_div.innerHTML = builderCommentText(builder_name);
builder_td.appendChild(tooltip_div);
}
// Only count the columns which are being displayed toward the total
// colspan.
if (allCategories[category] && allSubcategories[subcategory]) {
total_cols++;
}
}
// Only cound the subcategories which are being displayed toward the
// categories' colspans.
if (allCategories[category] && allSubcategories[subcategory]) {
category_colspan += subcategory_td.colSpan;
}
}
category_td.colSpan = category_colspan;
}
// Round the corners of the first and last visible category headers.
if (first_category) {
document.getElementById("category_header_" + first_category).classList.add("first");
}
if (last_category) {
document.getElementById("category_header_" + last_category).classList.add("last")
}
// Build rows for each revision.
for (var rev_index in revisions) {
// Information about this revision.
var revision = revisions[rev_index];
// String which alternates between "" and " Alt"; used for creating
// alternating shades of gray.
var alt = "";
if (rev_index % 2) {
alt = " Alt";
}
// Row containing the commit hash, committer, and statuses for each builder
// at this revision.
var rev_status_tr = table.insertRow(-1);
// Cell containing the commit hash with a link to the commit in the
// repository.
var revision_td = rev_status_tr.insertCell(-1);
revision_td.className = "DevRev" + alt;
var revision_link = newHyperlink(revision.repository + "/+/" + revision.id,
revision.id.substring(0, 11),
true);
revision_td.appendChild(revision_link);
// Cell containing the committer's username.
var dev_name_td = rev_status_tr.insertCell(-1);
dev_name_td.className = "DevName" + alt;
dev_name_td.width = "1%";
dev_name_td.innerHTML = revision.who;
// Loop over the builders, obtaining the status and creating a build status
// popup link for each.
for (var category in builders) {
for (var subcategory in builders[category]) {
for (var cat_full in builders[category][subcategory]) {
// Wrapper cell containing statuses for builders in this fully-
// qualified category.
var build_status_td = rev_status_tr.insertCell(-1);
// Hide the build status cell if we're not displaying both the
// builder's category and subcategory.
if (!allCategories[category] || !allSubcategories[subcategory]) {
build_status_td.style.display = "none";
}
build_status_td.className = "DevStatus category_" + category + "_subcategory_" + subcategory + alt;
// Table which will hold the individual builder statuses.
var build_status_wrapper = document.createElement("table");
build_status_wrapper.width = "100%";
build_status_td.appendChild(build_status_wrapper);
var builder_wrapper_tr = build_status_wrapper.insertRow(-1);
// Loop over the builders in this fully-qualified category.
for (var builder_index in builders[category][subcategory][cat_full]) {
var builder = builders[category][subcategory][cat_full][builder_index];
// Loop over the list of builds at this revision for this builder.
// This is almost always a single build.
for (var build_index in revision.builds[builder.builderName]) {
var build = revision.builds[builder.builderName][build_index];
// Create a colored cell representing the result of this build,
// with a link to open a popup with more detailed build step
// statuses.
var build_td = builder_wrapper_tr.insertCell(-1);
build_td.className = "DevStatusBox";
var build_link = newHyperlink("#", "", true);
build_link.popup_url = build.url;
build_link.onclick = function(event) { showBuildBox(this.popup_url, event); return false; };
build_link.title = build.pageTitle;
build_link.className = "DevStatusBox " + build.color + " " + build.tag;
build_td.appendChild(build_link);
}
}
}
}
}
// Row containing the change description for this revision.
var rev_comment_tr = table.insertRow(-1);
var rev_comment_td = rev_comment_tr.insertCell(-1);
rev_comment_td.className = "DevComment" + alt;
rev_comment_td.colSpan = total_cols;
// Split the comment text into: first line (short) and remainder (long).
var comment_short_text_span = document.createElement("span");
rev_comment_td.appendChild(comment_short_text_span);
var comment_long_text_span = document.createElement("span");
comment_long_text_span.id = revision.id + "_long_desc";
comment_long_text_span.style.display = "none";
// Create "more" and "less" links.
var more_link = document.createElement("a");
more_link.href = "javascript:toggleMore('" + revision.id + "', true)";
more_link.id = revision.id + "_morelink";
more_link.innerHTML = "more";
rev_comment_td.appendChild(more_link);
var less_link = document.createElement("a");
less_link.href = "javascript:toggleMore('" + revision.id + "', false)";
less_link.id = revision.id + "_lesslink";
less_link.style.display = "none";
less_link.innerHTML = "less";
rev_comment_td.appendChild(less_link);
rev_comment_td.appendChild(comment_long_text_span);
var comment_text_lines = revision.comments.split("\n");
// Only include the "more" link if the comment has more than one line.
if (comment_text_lines.length > 1) {
comment_short_text_span.innerHTML = comment_text_lines[0] + " ... ";
var comment_long_text_para = document.createElement("p");
comment_long_text_span.appendChild(comment_long_text_para);
for (var comment_line = 1; comment_line < comment_text_lines.length; ++comment_line) {
comment_long_text_span.innerHTML += comment_text_lines[comment_line] + "<br/>";
}
comment_long_text_span.innerHTML = addLinks(comment_long_text_span.innerHTML);
} else {
comment_short_text_span.innerHTML = revision.comments;
more_link.style.display = "none";
}
// Optional row containing more information about the revision, for example
// test failures.
if (revision["details"] && revision["details"].length > 0) {
var details_tr = table.insertRow(-1);
var details_td = details_tr.insertCell(-1);
details_td.colSpan = total_cols;
details_td.className = "DevDetails bottom" + alt;
var details_list = document.createElement("ul");
for (var detail_index in revision["details"]) {
var detail = revision["details"][detail_index];
var detail_item = document.createElement("li");
var detail_text = document.createElement("span");
detail_text.innerHTML = detail["buildername"] + ": " + detail["status"] + " - &nbsp;";
detail_item.appendChild(detail_text);
for (var log_index in detail["logs"]) {
var log = detail["logs"][log_index];
var log_link = document.createElement("a");
log_link.href = log["url"];
log_link.innerHTML = log["name"];
detail_item.appendChild(log_link);
}
details_list.appendChild(detail_item);
}
details_td.appendChild(details_list);
} else {
rev_comment_td.className += " bottom";
}
// Spacing row just containing whitespace.
var rev_spacing_tr = table.insertRow(-1);
rev_spacing_tr.className = "DevStatusSpacing";
var rev_spacing_td = rev_spacing_tr.insertCell(-1);
}
}
// ]]>
</script>
{% endblock %}
{% block content %}
<h1>Console View</h1>
<div id="loading_div"></div>
<div align="center">
<table width="95%" class="Grid" border="0" cellspacing="0">
<tr>
<td width="33%" align="left" class="left_align">
{% if repository %}
<br><b>Repository:</b> {{ repository|e }}
{% endif %}
</td>
<td width="33%" align="center" class="center_align">
<div align="center">
<table class="info">
<tr>
<td>Legend:&nbsp;&nbsp;</td>
<td class='legend success' title='All tests passed'>Passed</td>
<td class='legend failure' title='There is a new failure. Take a look!'>Failed</td>
<td class='legend warnings' title='It was failing before, and it is still failing. Make sure you did not introduce new regressions'>Failed&nbsp;Again</td>
<td class='legend running' title='The tests are still running'>Running</td>
<td class='legend running_failure' title='The tests are still running, but at least one step has failed.'>Running&nbsp;w/&nbsp;failures</td>
<td class='legend exception' title='Something went wrong with the test, there is no result'>Exception</td>
<td class='legend offline' title='The builder is offline, as there are no slaves connected to it'>Offline</td>
<td class='legend notstarted' title='No result yet.'>No&nbsp;data</td>
<td class='legend BuilderHasComment' title='This builder has a comment.'>Builder comment</td>
</tr>
</table>
</div>
</td>
<td width="33%" align="right" class="right_align">
<script type="text/javascript">
// <![CDATA[
function reload_page() {
name_value = document.getElementById('namebox').value
if (document.location.href.lastIndexOf('?') == -1)
document.location.href = document.location.href+ '?name=' + name_value;
else
document.location.href = document.location.href+ '&name=' + name_value;
}
// ]]>
</script>
<input id='namebox' name='name' type='text' style='color:#999;'
onblur='this.value = this.value || this.defaultValue; this.style.color = "#999";'
onfocus='this.value=""; this.style.color = "#000";'
value='Personalized for...'/>
<input type='submit' value='Go' onclick='reload_page()'/>
</td>
</tr>
</table>
</div>
<br/>
<div align="center">
<table id="console_table" width="96%"></table>
</div>
<div id="divBox" onmouseout="if (checkMouseLeave(this, event)) this.style.display = 'none'" class="BuildWaterfall" style="display: none;">
</div>
<iframe id="frameBox" style="display: none;"></iframe>
<script type="text/javascript">
// replace 'onload="updateDiv(event);" with this, as iframe doesn't have onload event in xhtml
window.onload = function() {
document.getElementById('frameBox').onload = function(event) {
updateDiv(event);
};
};
</script>
{% endblock %}
{% block footer %}
{{ super() }}
{# <p>Debug info: {{ debuginfo }}</p> #}
{% endblock %}