github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/gubernator/static/build.js (about)

     1  // Given a DOM node, attempt to select it.
     2  function select(node) {
     3  	var sel = window.getSelection();
     4  	if (sel.toString() !== "") {
     5  		// User is already trying to do a drag-selection, don't prevent it.
     6  		return;
     7  	}
     8  	// Works in Chrome/Safari/FF/IE10+
     9  	var range = document.createRange();
    10  	range.selectNode(node);
    11  	sel.removeAllRanges();
    12  	sel.addRange(range);
    13  }
    14  
    15  // Rewrite timestamps to respect the current locale.
    16  function fix_timestamps() {
    17  	function replace(className, fmt) {
    18  		var tz = moment.tz.guess();
    19  		var els = document.getElementsByClassName(className);
    20  		for (var i = 0; i < els.length; i++) {
    21  			var el = els[i];
    22  			var epoch = el.getAttribute('data-epoch');
    23  			if (epoch) {
    24  				var time = moment(1000 * epoch).tz(tz);
    25  				if (typeof fmt === 'function') {
    26  					el.innerText = fmt(time);
    27  				} else {
    28  					el.innerText = time.format(fmt);
    29  				}
    30  			}
    31  		}
    32  	}
    33  	replace('timestamp', 'YYYY-MM-DD HH:mm z')
    34  	replace('shorttimestamp', 'MM-DD HH:mm')
    35  	replace('humantimestamp', function(t) {
    36  		var fmt = 'MMM D, Y';
    37  		if (t.isAfter(moment().startOf('day'))) {
    38  			fmt = 'h:mm A';
    39  		} else if (t.isAfter(moment().startOf('year'))) {
    40  			fmt = 'MMM D';
    41  		}
    42  		return t.format(fmt);
    43  	})
    44  }
    45  
    46  var get_cache = {};
    47  
    48  // Download a file from GCS or elsewhere, and run "callback" with its contents.
    49  function get(uri, callback) {
    50  	if (get_cache[uri]) {
    51  		callback(get_cache[uri]);
    52  		return;
    53  	}
    54  	if (uri[0] === '/') {
    55  		// Matches gs://bucket/file/path -> [..., "bucket", "file/path"]
    56  		//         /bucket/file/path -> [..., "bucket", "file/path"]
    57  		var groups = uri.match(/([^/:]+)\/(.*)/);
    58  		var bucket = groups[1], path = groups[2];
    59  		var url = 'https://www.googleapis.com/storage/v1/b/' + bucket + '/o/' +
    60  			encodeURIComponent(path) + '?alt=media';
    61  	} else {
    62  		var url = uri;
    63  	}
    64  	var req = new XMLHttpRequest();
    65  	req.open('GET', url);
    66  	req.onload = function(resp) {
    67  		get_cache[uri] = {status: req.status, response: req.response};
    68  		callback(req);
    69  	}
    70  	req.send();
    71  }
    72  
    73  function expand_lines(els, data) {
    74  	var lines = data.split('\n');
    75  	var parent = els[0].parentElement;
    76  	for (var i = 0; i < els.length; i++) {
    77  		var el = els[i];
    78  		var range = el.dataset['range'].split('-');
    79  		var chunk = lines.slice(range[0], range[1]);
    80  		var chunk = chunk.join('\n');
    81  		if (el.previousSibling) {
    82  			el.previousSibling.appendData(chunk);
    83  			el.remove();
    84  		} else if (el.nextSibling) {
    85  			el.nextSibling.data = chunk + el.nextSibling.data;
    86  			el.remove();
    87  		}
    88  	}
    89  	parent.normalize();  // merge adjacent text nodes
    90  	fix_escape_codes();  // colorize new segments
    91  }
    92  
    93  function expand_skipped(els) {
    94  	var src = els[0].parentElement.dataset['src'];
    95  	get(src, function(req) {
    96  		if (req.status == 401) {  // unauthorized
    97  			// try proxying through Gubernator
    98  			var proxyUrl = document.location.origin + '/gcsproxy?path=' + escape(src);
    99  			document.getElementById('rawloglink').href = proxyUrl
   100  			get(proxyUrl, function (req) { expand_lines(els, req.response); })
   101  		} else {
   102  			expand_lines(els, req.response);
   103  		}
   104  	});
   105  	document.querySelector('h2#log').innerHTML = 'Build Log';
   106  }
   107  
   108  function expand_all(btn) {
   109  	var logs = document.querySelectorAll('pre[data-src]');
   110  	for (var i = 0; i < logs.length; i++) {
   111  		var skips = logs[i].querySelectorAll('span.skip');
   112  		if (skips.length > 0) {
   113  			expand_skipped(skips);
   114  		}
   115  	}
   116  	btn.remove();
   117  }
   118  
   119  function expand_element(els) {
   120  	var parent = els[0].parentElement;
   121  	var hidden = parent.querySelectorAll(".hidden");
   122  	for (var i = 0; i < hidden.length; i++) {
   123  		hidden[i].classList.toggle("hidden");
   124  	}
   125  	els[0].classList.add("hidden");
   126  }
   127  
   128  /* given a string containing ansi formatting directives, return a new one
   129     with designated regions of text marked with the appropriate color directives,
   130     and with all unknown directives stripped */
   131  function ansi_to_html(orig) {
   132  	// Given a cmd (like "32" or "0;97"), some enclosed body text, and the original string,
   133  	// either return the body wrapped in an element to achieve the desired result, or the
   134  	// original string if nothing works.
   135  	function annotate(cmd, body, orig) {
   136  		var code = +(cmd.replace('0;', ''));
   137  		if (code === 0) // reset
   138  			return body;
   139  		else if (code === 1) // bold
   140  			return '<em>' + body + '</em>';
   141  		else if (30 <= code && code <= 37) // foreground color
   142  			return '<span class="ansi-' + (code - 30) + '">' + body + '</span>'
   143  		else if (90 <= code && code <= 97) // foreground color, bright
   144  			return '<span class="ansi-' + (code - 90 + 8) + '">' + body + '</span>'
   145  		return body;  // fallback: don't change anything
   146  	}
   147  	// Find commands, optionally followed by a bold command, with some content, then a reset command.
   148  	// Unpaired commands are *not* handled here, but they're very uncommon.
   149  	var filtered = orig.replace(/\033\[([0-9;]*)\w(\033\[1m)?([^\033]*?)\033\[0m/g, function(match, code, bold, body, offset, string) {
   150  		if (bold !== undefined)  // normal code + bold
   151  			return '<em>' + annotate(code, body, string) + '</em>';
   152  		return annotate(code, body, string);
   153  	})
   154  	// Strip out anything left over.
   155  	return filtered.replace(/\033\[([0-9;]*\w)/g, function(match, cmd, offset, string) {
   156  		console.log('unhandled ansi code: ', cmd, "context:", JSON.stringify(filtered.slice(offset-50,offset+50)));
   157  		return '';
   158  	});
   159  }
   160  
   161  function fix_escape_codes() {
   162  	var logs = document.querySelectorAll('pre[data-src]');
   163  	for (var i = 0; i < logs.length; i++) {
   164  		var orig = logs[i].innerHTML;
   165  		var newer = ansi_to_html(orig);
   166  		if (orig !== newer) {
   167  			logs[i].innerHTML = newer;
   168  		}
   169  	}
   170  }
   171  
   172  /* Remove unicode sequences caused by colorized output in junit.xml */
   173  function remove_unicode_escape_codes() {
   174  	var errors = document.querySelectorAll('pre.error')
   175  	for (var i = 0; i < errors.length; i++) {
   176  		var orig = errors[i].innerHTML
   177  		var newer = orig.replace(/\ufffd\[\d+m/g, "")
   178  		if (orig !== newer) {
   179  			errors[i].innerHTML = newer;
   180  		}
   181  	}
   182  }
   183  
   184  function init() {
   185  	fix_timestamps();
   186  	fix_escape_codes();
   187  	remove_unicode_escape_codes();
   188  	document.body.onclick = function(evt) {
   189  		var target = evt.target;
   190  		if (target.nodeName === 'SPAN' && target.classList.contains('skip')) {
   191  			expand_skipped([target]);
   192  			evt.preventDefault();
   193  		}
   194  		if (target.nodeName === 'SPAN' && target.classList.contains('expand')) {
   195  			expand_element([target]);
   196  			evt.preventDefault();
   197  		}
   198  	}
   199  }
   200  
   201  if (typeof module !== 'undefined' && module.exports) {
   202  	// enable node.js `require('./build')` to work for testing
   203  	module.exports = {
   204  		ansi_to_html: ansi_to_html
   205  	}
   206  }
   207  
   208  // Acknowledge a PR to suppress it. If repo is "CLEAR", clear acks instead.
   209  function ack(event, repo, number, latest) {
   210  	event.stopPropagation();
   211  	var req = new XMLHttpRequest();
   212  	req.open('POST', '/pr');
   213  	req.onload = function(resp) {
   214  		if (req.status != 200) return;
   215  		var row = document.getElementById('needs-attention ' + repo + ' ' + number);
   216  		if (row) {
   217  			row.remove();
   218  		}
   219  	}
   220  	req.send(JSON.stringify({
   221  		command: 'ack',
   222  		repo: repo,
   223  		number: number,
   224  		latest: latest,
   225  	}));
   226  }
   227  
   228  // Reset the acknowledged PRs
   229  function ack_clear() {
   230  	var req = new XMLHttpRequest();
   231  	req.open('POST', '/pr');
   232  	req.onload = function(resp) {
   233  		if (req.status != 200) return;
   234  		document.location = document.location;  // refresh
   235  	}
   236  	req.send(JSON.stringify({
   237  		command: 'ack-clear',
   238  	}));
   239  }