diff options
author | 2015-04-14 10:48:09 +0000 | |
---|---|---|
committer | 2015-04-14 14:33:09 +0000 | |
commit | 38d6f3c5b0a0e8d77c041b72002f2dcdc5468e53 (patch) | |
tree | 2f5883b93ac0c6bd9318acb4e27550a9f1b1e5fe | |
parent | e6459a9fd8ee191dc11ce56dc549d45d42226da7 (diff) |
Enable external contribution to src/main/java/**
Also removed useless bazel-constants-srcjar.sh
--
MOS_MIGRATED_REVID=91075224
6 files changed, 541 insertions, 21 deletions
diff --git a/src/main/java/bazel-constants-srcjar.sh b/src/main/java/bazel-constants-srcjar.sh deleted file mode 100755 index 1dfddfa666..0000000000 --- a/src/main/java/bazel-constants-srcjar.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -euo pipefail - -OUTPUT=${PWD}/$1 -shift -ZIP=${PWD}/$1 -shift -UNZIP=${PWD}/$1 -shift -CONSTANTS_JAVA=${PWD}/$1 - -TMPDIR=$(mktemp -d) -trap "rm -rf $TMPDIR" EXIT - -OUTPUT_CONSTANTS=$TMPDIR/java/com/google/devtools/build/lib/Constants.java - -mkdir -p $(dirname $OUTPUT_CONSTANTS) -cp $CONSTANTS_JAVA $OUTPUT_CONSTANTS - -cd $TMPDIR -$ZIP -jt -qr $OUTPUT . diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.html b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.html new file mode 100644 index 0000000000..f57bc30c8e --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.html @@ -0,0 +1,14 @@ +<html> +<head> + <title> Bazel web server </title> + <link rel="stylesheet" type="text/css" href="/css/style.css"></link> + <script src="/js/lib/d3.js" type="application/javascript"></script> + <script src="/js/lib/jquery.js" type="application/javascript"></script> + <script src="/js/index.js" type="application/javascript"></script> +</head> +<body onload="showData()"> + <h1> Bazel web server status page </h1> + <div id="testsList"> + </div> +</body> +</html> diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.js b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.js new file mode 100644 index 0000000000..4ef967111c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.js @@ -0,0 +1,76 @@ +var icons = { + running: '\u25B6', + finished: '\u2611' +}; + +function showData() { + renderTestList(getTestsData()); +} + +function getTestsData() { + // TODO(bazel-team): change it to async callback retrieving data in background + // (for simplicity this is synchronous now) + return $.ajax({ + type: 'GET', + url: document.URL + 'tests/list', + async: false + }).responseJSON; +} + +function renderTestList(tests) { + var rows = d3.select('#testsList') + .selectAll() + .data(tests) + .enter().append('div') + .classed('info-cell', true); + + // status + rows.append('div').classed('info-detail', true).text(function(j) { + return j.finished ? icons.finished : icons.running; + }); + + // target(s) name(s) + rows.append('div').classed('info-detail', true).text(function(j) { + if (j.targets.length == 1) { + return j.targets[0]; + } + if (j.targets.length == 0) { + return 'Unknown target.'; + } + return j.targets; + }); + + // start time + rows.append('div').classed('info-detail', true).text(function(j) { + // Pad value with 2 zeroes + function pad(value) { + return value < 10 ? '0' + value : value; + } + + var + date = new Date(j.startTime), + today = new Date(Date.now()), + h = pad(date.getHours()), + m = pad(date.getMinutes()), + dd = pad(date.getDay()), + mm = pad(date.getMonth()), + yy = date.getYear(), + day; + + // don't show date if ran today + if (dd != today.getDay() && mm != today.getMonth() && + yy != today.getYear()) { + day = ' on ' + yy + '-' + mm + '-' + dd; + } else { + day = ''; + } + return h + ':' + m; + }); + + // link + rows.append('div').classed('info-detail', true).classed('button', true) + .append('a').attr('href', function(datum, index) { + return '/tests/' + datum.uuid; + }) + .text('link'); +} diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/static/style.css b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/style.css new file mode 100644 index 0000000000..2ea712d760 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/style.css @@ -0,0 +1,39 @@ +a { + color: #666 +} +body { + font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, sans-serif; +} +.test-case { + outline:1px solid #eeeeee; + display:block; + padding:5px +} +.test-case:nth-child(odd) { + background: #eee +} +.test-case:nth-child(even) { + background: #fff +} +.test-detail { + display:inline-block; + min-width:10px; + padding-left:5px; + padding-right:5px +} +.info-cell { + padding:5px; + background: #eee; + outline:1px solid #fff; + display:block +} +.info-detail { + padding-right:5px; + padding-left:5px; + display:inline-block +} +.button { + cursor:pointer; + color:#666; + float:right +} diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.html b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.html new file mode 100644 index 0000000000..04a6fb712c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.html @@ -0,0 +1,28 @@ +<html> +<head><title>Tests Result Page</title> + <link rel="stylesheet" type="text/css" href="/css/style.css"></link> + <script src="/js/lib/d3.js" type="application/javascript"></script> + <script src="/js/lib/jquery.js" type="application/javascript"></script> + <script src="/js/test.js" type="application/javascript"></script> +</head> +<body> +<h1> Bazel web status server </h1> +<div id="testInfo"> + No test info to display. +</div> +<br> +<div id="testFilters"> + <div class="info-cell"> + <input placeholder="Filter by name" type=text id="search"></input> + <!-- TODO(bazel-team) this is very simplistic view of tests, + we probably need more filters --> + <input type=checkbox checked=true id="boxPassed">passed</input> + <input type=checkbox checked=true id="boxFailed">failed</input> + <button id="clearFilters"> clear filters </button> + </div> +</div> +<div id="testDetails"> + No test details to display. +</div> +</body> +</html> diff --git a/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.js b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.js new file mode 100644 index 0000000000..406dcabd55 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.js @@ -0,0 +1,384 @@ +var icons = { + running: '?', + passed: '\u2705', + errors: '\u274c' +}; + + +function showData() { + renderDetails(getDetailsData(), false); + renderInfo(getCommandInfo()); +} + +function getCommandInfo() { + var url = document.URL; + if (url[url.length - 1] != '/') { + url += '/'; + } + return $.ajax({ + type: 'GET', + url: url + 'info', + async: false + }).responseJSON; +} + +function getDetailsData() { + // TODO(bazel-team): auto refresh, async callback + var url = document.URL; + if (url[url.length - 1] != '/') { + url += '/'; + } + return $.ajax({ + type: 'GET', + url: url + 'details', + async: false + }).responseJSON; +} + + +function showDate(d) { + function pad(x) { + return x < 10 ? '0' + x : '' + x; + } + var today = new Date(); + var result = pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + + pad(d.getSeconds()); + if (d.getDate() === today.getDate() && d.getMonth() === today.getMonth() && + d.getYear() === today.getYear()) { + result += ' today'; + } else { + result += pad(d.getDate()) + ' ' + pad(d.getMonth()) + ' ' + d.getYear(); + } + return result; +} + +function renderInfo(info) { + $('#testInfo').empty(); + var data = [ + ['Targets: ', info['targets']], + ['Started at: ', showDate(new Date(info['startTime']))], + ]; + if (info['finished']) { + data.push(['Finished at: ', showDate(new Date(info['endTime']))]); + } else { + data.push(['Still running']); + } + var selection = d3.select('#testInfo').selectAll() + .data(data) + .enter().append('div') + .classed('info-cell', true); + selection + .append('div') + .classed('info-detail', true) + .text(function(d) { return d[0]; }); + selection + .append('div') + .classed('info-detail', true) + .text(function(d) { return d[1]; }); +} + +// predicate is either a predicate function or null - in the latter case +// everything is shown +function renderDetails(tests, predicate) { + $('#testDetails').empty(); + if (tests.length == 0) { + $('#testDetails').text('No test details to display.'); + return; + } + // flatten object to array and set visibility + tests = $.map(tests, function(element) { + if (predicate) { + setVisibility(predicate, element); + } + return element; + }); + var rows = d3.select('#testDetails').selectAll() + .data(tests) + .enter().append('div') + .classed('test-case', true); + + function addTestDetail(selection, toplevel) { + function fullName() { + selection.append('div').classed('test-detail', true).text(function(j) { + return j.fullName; + }); + } + function propagateStatus(j) { + var result = ''; + var failures = []; + var errors = []; + $.each(j.results, function(key, value) { + if (value == 'FAILED') { + failures.push(key); + } + if (value == 'ERROR') { + errors.push(key); + } + }); + if (failures.length > 0) { + var s = failures.length > 1 ? 's' : ''; + result += 'Failed on ' + failures.length + ' shard' + s + ': ' + + failures.join(); + } + if (errors.length > 0) { + var s = failures.length > 1 ? 's' : ''; + result += 'Errors on ' + errors.length + ' shard' + s + ': ' + + errors.join(); + } + if (result == '') { + return j.status; + } + return result; + } + function testCaseStatus() { + selection.append('div') + .classed('test-detail', true) + .text(propagateStatus); + } + function testTargetStatus() { + selection.append('div') + .classed('test-detail', true) + .text(function(target) { + var childStatus = propagateStatus(target); + if (target.finished = false) { + return target.status + ' ' + stillRunning; + } else { + if (childStatus == 'PASSED') { + return target.status; + } else { + return target.status + ' ' + childStatus; + } + } + }); + } + function testTargetStatusIcon() { + selection.append('div') + .classed('test-detail', true) + .attr('color', function(target) { + var childStatus = propagateStatus(target); + if (target.finished == false) { + return 'running'; + } else { + if (childStatus == 'PASSED') { + return 'passed'; + } else { + return 'errors'; + } + }}) + .text(function(target) { + var childStatus = propagateStatus(target); + if (target.finished == false) { + return icons.running; + } else { + if (childStatus == 'PASSED') { + return icons.passed; + } else { + return icons.errors; + } + } + }); + } + function testCaseTime() { + selection.append('div').classed('test-detail', true).text(function(j) { + var times = $.map(j.times, function(element, key) { return element }); + if (times.length < 1) { + return '?'; + } else { + return Math.max.apply(Math, times) / 1000 + ' s'; + } + }); + } + + function visibilityFilter() { + selection.attr('show', function(datum) { + return ('show' in datum) ? datum['show'] : true; + }); + } + + // Toplevel nodes represent test targets, so they look a bit different + if (toplevel) { + testTargetStatusIcon(); + fullName(); + } else { + testTargetStatusIcon(); + fullName(); + testCaseStatus(); + testCaseTime(); + } + visibilityFilter(); + } + + function addNestedDetails(table, toplevel) { + table.sort(function(data1, data2) { + if (data1.fullName < data2.fullName) { + return -1; + } + if (data1.fullName > data2.fullName) { + return 1; + } + return 0; + }); + + addTestDetail(table, toplevel); + + // Add children nodes + show/hide button + var nonLeafNodes = table.filter(function(data, index) { + return !($.isEmptyObject(data.children)); + }); + var nextLevelNodes = nonLeafNodes.selectAll().data(function(d) { + return $.map(d.children, function(element, key) { return element }); + }); + + if (nextLevelNodes.enter().empty()) { + return; + } + + nonLeafNodes + .append('div') + .classed('test-detail', true) + .classed('button', true) + .text(function(j) { + return 'Show details'; + }) + .attr('toggle', 'off') + .on('click', function(datum) { + if ($(this).attr('toggle') == 'on') { + $(this).siblings('.test-case').not('[show=false]').hide(); + $(this).attr('toggle', 'off'); + $(this).text('Show details'); + } else { + $(this).siblings('.test-case').not('[show=false]').show(); + $(this).attr('toggle', 'on'); + $(this).text('Hide details'); + } + }); + nextLevelNodes.enter().append('div').classed('test-case', true); + addNestedDetails(nextLevelNodes, false); + } + + addNestedDetails(rows, true); + $('.button').siblings('.test-case').hide(); + if (predicate) { + toggleVisibility(); + } +} + +function toggleVisibility() { + $('#testDetails > [show=false]').hide(); + $('#testDetails > [show=true]').show(); + $('[toggle=on]').siblings('[show=false]').hide(); + $('[toggle=on]').siblings('[show=true]').show(); +} + +function setVisibility(predicate, object) { + var show = predicate(object); + var childrenPredicate = predicate; + // It rarely makes sense to show a non-leaf node and hide its children, so + // we just show all children + if (show) { + childrenPredicate = function() { return true; }; + } + if ('children' in object) { + for (var child in object.children) { + setVisibility(childrenPredicate, object.children[child]); + show = object.children[child]['show'] || show; + } + } + object['show'] = show; +} + +// given a list of predicates, return a function +function intersectFilters(filterList) { + var filters = filterList.filter(function(x) { return x }); + return function(x) { + for (var i = 0; i < filters.length; i++) { + if (!filters[i](x)) { + return false; + } + } + return true; + } +} + +function textFilterActive() { + return $('#search').val(); +} + +function getTestFilters() { + var statusFilter = null; + var textFilter = null; + var filters = []; + var passed = $('#boxPassed').prop('checked'); + var failed = $('#boxFailed').prop('checked'); + // add checkbox filters only when necessary (ie. something is unchecked - when + // everything is checked this means user wants to see everything). + if (!(passed && failed)) { + var checkBoxFilters = []; + if (passed) { + checkBoxFilters.push(function(object) { + return object.status == 'PASSED'; + }); + } + if (failed) { + checkBoxFilters.push(function(object) { + return 'status' in object && object.status != 'PASSED'; + }); + } + filters.push(function(object) { + return checkBoxFilters.some(function(f) { return f(object); }); + }); + } + if (textFilterActive()) { + filters.push(function(object) { + // TODO(bazel-team): would case insentive search make more sense? + return ('fullName' in object && + object.fullName.indexOf($('#search').val()) != -1); + }); + } + return filters; +} + +function redraw() { + renderDetails(getDetailsData(), intersectFilters(getTestFilters())); +} + +function updateVisibleCases() { + var predicate = intersectFilters(getTestFilters()); + var parentCases = d3.selectAll('#testDetails > div').data(); + parentCases.forEach(function(element, index) { + setVisibility(predicate, element); + }); + d3.selectAll('.test-detail').attr('show', function(datum) { + return ('show' in datum) ? datum['show'] : true; + }); + d3.selectAll('.test-case').attr('show', function(datum) { + return ('show' in datum) ? datum['show'] : true; + }); + toggleVisibility(); + if (textFilterActive()) { + // expand nodes to save some clicking - if user searched for something that + // is leaf of the tree, she definitely wants to see it + $('#testDetails > [show=true]').find('[toggle=off]').click(); + } +} + +function enableControls() { + var redrawTimeout = null; + $('#boxPassed').click(updateVisibleCases); + $('#boxFailed').click(updateVisibleCases); + $('#search').keyup(function() { + clearTimeout(redrawTimeout); + redrawTimeout = setTimeout(updateVisibleCases, 500); + }); + $('#clearFilters').click(function() { + $('#boxPassed').prop('checked', true); + $('#boxFailed').prop('checked', true); + $('#search').val(''); + updateVisibleCases(); + }); +} + +$(function() { + showData(); + enableControls(); +}); |