blob: 89eb6fd964dfd99aaf2d2ee56df755a6504e3c5d [file] [log] [blame]
<!--
status_utils.js must be included before this.
This in an HTML Import-able file that contains the definition
of the following elements:
<buildbot-table-sk>
To use this file import it:
<link href="/res/imp/buildbot-table-sk.html" rel="import" />
Usage:
<buildbot-table-sk></buildbot-table-sk>
Properties:
// input
keys: Array<String>, the keys of the data to be displayed
values: Object{String: Array<String>}, the values corresponding to keys to be displayed
builds: Object, the builds, corresponding to key-value pairs. It has the following layout:
{
Key: {
Value: Array<{
_id: Number, a unique number, in that every build should have a different one.
filler: Boolean, if this represents a "filler" build, so as to space things correctly.
Number: Number, build number, used for urls and identifying builds.
Builder: String, builder, used for urls and identifying builds.
Buildslave: String, buildslave, used for urls and identifying builds.
Finished: String, The date string the build was finished, or falsey
Results: Number, one of the buildbot magic numbers
}>
}
}
urls: Object, the base urls used to create links. It has the following layout:
{
Key: {
key: The base url for keys. 'key' will be appended to this.
value: The base url for values. 'value' will be appended to this.
base: The following will be appended to this base url:
/builders/[build.Builder]/builds/[build.Number]
}
}
// output
drawing: Boolean, if the table is being drawn.
Methods:
None.
Events:
None.
-->
<link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
<script src="/res/imp/bower_components/d3/d3.min.js"></script>
<!--
Styles for the internal divs are kept in a separate
CSS file since we are using d3 to manage those elements and not the
Polymer.dom() functions, so the whole magic CSS scoping stuff doesn't
work, so we just prefix every rule with build-table-sk.
-->
<link rel="stylesheet" href="build-table.css">
<dom-module id="build-table-sk">
<template>
<style>
#buildTable {
font-size: 12px;
margin: 5px;
max-width: inherit;
min-width: inherit;
}
</style>
<div id="buildTable"></div>
</template>
<script>
(function(){
// Identifying data allows us to figure out which dom elements are already drawn
// or need to be removed.
// uniquely identify the keys data using the key itself, not index in array,
// which is the default.
var keyDataFunction = function(key){
return key;
};
// uniquely identify the values data using the value itself, not index in array,
// which is the default.
var valueDataFunction = function(value){
return value;
};
// uniquely identify the builds using the the _id, BuildSlave,Builder and Number, not index in
// array, which is the default.
var buildDataFunction = function(build){
return build._id + build.BuildSlave + build.Builder + build.Number;
};
Polymer({
is:"build-table-sk",
properties: {
// input
keys: {
type: Array,
},
values: {
type: Object,
},
builds: {
type: Object,
},
urls: {
type: Object,
},
// output
drawing: {
type: Boolean,
notify: true,
value: false,
}
},
observers: [
"redraw(keys, values, builds, urls)"
],
redraw:function(keys, values, builds, urls) {
// This gets called any time one of the values changes. Since the values are
// updated simultaneously, we don't want to try to draw the table 4 times at the
// same time, so we debounce it. No timeout on the debounce because all the this.set
// calls will happen before a paint call, so this just basically collates all the requests.
this.debounce("redraw-build-table", function(){
this._redraw(keys,values,builds,urls);
}.bind(this));
},
_redraw:function(keys, values, builds, urls) {
this.set("drawing", true);
this._removeOldData(keys, values, builds);
console.time("(re)drawing table starting with "+keys[0]);
var start = 0;
var end = 0;
// We draw this in chunks to give the feel of responsive-ness for large data chunks.
d3.timer(function(){
start = end;
end = Math.min(keys.length, end + 10);
if (start >= end) {
this.set("drawing", false);
console.timeEnd("(re)drawing table starting with "+keys[0]);
// d3.timer ends upon returning true.
return true;
}
console.log("frame "+start+"-"+end);
var _keys = keys.slice(0, end);
// Create a data binding between keys and div.row
var data = d3.select(this.$.buildTable)
.selectAll(".row")
.data(_keys, keyDataFunction);
// enter() returns placeholders for any element that doesn't exist but has a data point.
// We can use these placeholders to create actual divs.
// rows are any divs that were freshly created in this way.
var rows = data
.enter()
.append("div")
.classed("row", true);
// Resort the rows by key order, which should be alphabetical or some other specified,
// sensical order.
data.order();
// For every new row, add div.key, div.values and div.builds as inner html.
rows
.insert("div")
.classed("key", true)
.html(function(key){
return "<a target='_blank' class='plain' href="+urls[key].key+">"+key+"</a>";
});
rows
.insert("div")
.classed("values", true);
rows
.insert("div")
.classed("builds", true);
// For every div.values, bind the values to it. If there are new values, create a
// div.valuename with the value anchor as content.
d3.select(this.$.buildTable).selectAll(".values").each(function(key) {
if (!values[key]) {
return;
}
d3.select(this)
.selectAll("div")
.data(values[key], valueDataFunction)
.enter()
.append("div")
.classed("valuename", true)
.html(function(value){
return "<a target='_blank' class='plain' href="+urls[key].value + value+">"+value+"</a>";
});
});
// For every div.builds, bind the key and the value to it. If there are new key-value
// pairs, create a div.buildrow with no content.
d3.select(this.$.buildTable).selectAll(".builds").each(function(key) {
if (!values[key]) {
return;
}
d3.select(this)
.selectAll("div")
.data(values[key].map(function(value) {
return {
"key": key,
"value": value,
};
}), function(d){
return d.key + d.value;
})
.enter()
.append("div")
.classed("buildrow", true);
});
// For every div.buildrow, bind the builds to it. If there are new builds, create a
// div.build with the build number anchor tag as content. This will be styled according
// to the build.
d3.select(this.$.buildTable).selectAll(".buildrow").each(function(d) {
if (!builds[d.key] || !builds[d.key][d.value]) {
return;
}
var data = d3.select(this)
.selectAll("div")
.data(builds[d.key][d.value], buildDataFunction);
data
.enter()
.append("div")
.classed("build", true)
.classed("filler", function(build) {
return build.filler;
})
.classed("success", function(build) {
return build.Results == BUILDBOT_RESULT_SUCCESS || build.Results == BUILDBOT_RESULT_WARNINGS || build.Results == BUILDBOT_RESULT_SKIPPED;
})
.classed("failure", function(build){
return build.Results == BUILDBOT_RESULT_FAILURE;
})
.classed("exception", function(build){
return build.Results == BUILDBOT_RESULT_EXCEPTION || build.Results == BUILDBOT_RESULT_CANCELLED;
})
.classed("inprogress", function(build){
return build.Finished == BUILDBOT_PENDING;
})
.style("min-width", function(build){
// TODO(skia:4739)
return "32px";
})
.html(function(build){
if (build.filler) {
return "";
}
return "<a target='_blank' class='plain' href="+encodeURI(urls[d.key].base)+"builders/"+encodeURI(build.Builder)+"/builds/"+build.Number+">"+build.Number+"</a>";
});
data.order();
});
this._fixClippedDivs();
}.bind(this), 10);
},
_removeOldData: function(keys, values, builds) {
console.time("removing data that no longer need showing");
// By binding the data to the objects that we create in _redraw, we can remove those
// elements for which the data has been filtered out (i.e. no longer exists). The .exit()
// returns all elements for which there once was data, but is no longer.
d3.select(this.$.buildTable)
.selectAll(".row")
.data(keys, keyDataFunction)
.exit()
.remove();
d3.select(this.$.buildTable).selectAll(".values").each(function(key) {
if (!values[key]) {
return;
}
d3.select(this)
.selectAll("div")
.data(values[key], valueDataFunction)
.exit()
.remove();
});
d3.select(this.$.buildTable).selectAll(".buildrow").each(function(d) {
if (!builds[d.key] || !builds[d.key][d.value]) {
d3.select(this).remove();
return;
}
d3.select(this)
.selectAll(".build")
.data(builds[d.key][d.value], buildDataFunction)
.exit()
.remove();
});
console.timeEnd("removing data that no longer need showing");
},
// Fix the horizontal lines, which will be clipped unless the width is set manually.
// http://stackoverflow.com/a/21540207
_fixClippedDivs: function() {
d3.select(this.$.buildTable).selectAll(".buildrow").style("width",function(d){
if (this.parentElement.scrollWidth) {
// Account for padding on .buildrow
return this.parentElement.scrollWidth +"px";
}
});
},
});
})()
</script>
</dom-module>