aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Damien Martin-Guillerez <dmarting@google.com>2015-04-14 10:48:09 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2015-04-14 14:33:09 +0000
commit38d6f3c5b0a0e8d77c041b72002f2dcdc5468e53 (patch)
tree2f5883b93ac0c6bd9318acb4e27550a9f1b1e5fe
parente6459a9fd8ee191dc11ce56dc549d45d42226da7 (diff)
Enable external contribution to src/main/java/**
Also removed useless bazel-constants-srcjar.sh -- MOS_MIGRATED_REVID=91075224
-rwxr-xr-xsrc/main/java/bazel-constants-srcjar.sh21
-rw-r--r--src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.html14
-rw-r--r--src/main/java/com/google/devtools/build/lib/webstatusserver/static/index.js76
-rw-r--r--src/main/java/com/google/devtools/build/lib/webstatusserver/static/style.css39
-rw-r--r--src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.html28
-rw-r--r--src/main/java/com/google/devtools/build/lib/webstatusserver/static/test.js384
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();
+});