| <!-- |
| 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> |