refactor of hideci for readability and modularity

This is a refactors the hideci code into much smaller functions,
making the whole thing a bit more readable for fixing this going
forward. This was done in order to add a couple of new features. The
smaller functions should also make this code much easier to review.

After the refactor we now do a top down parsing of all comments in a
review, classifying them as 'is_ci' and 'is_trusted_ci' so that we can
more easily compute the results we want going forward.

In the process a few additional features were added.

1) CI results are only shown for the latest patch set, even if no CI
system has reported on it. This is accomplished via
ci_latest_patchset.

2) If CI system is in merge conflict, that's displayed in the results
box.

3) Test results records are lifted in total to the test results
display panel, and each set of test results includes the date that we
got it from. This means that for patches that get 'rechecked' a lot,
you'll see the whole history of test results on current patch. You
will also be able to quickly realize that the current test results are
older than the last recheck comment.

The UX for part 3 is different, and may need tweaking going forward,
however the merge of results from different runs was creating a lot of
confusion to reviewers, so this errors on the side of more clarity.

Because of the largely inconvenient workflow for development (the fact
that you basically need to copy / paste function definitions into
chrome dev console) this isn't broken up as smaller patches. This
should, hopefully, make future patches much easier on this code
though.

Change-Id: I3f128a449756fd6988f3810863cd1d49c476b0dd
This commit is contained in:
Sean Dague 2014-09-28 09:32:51 -04:00
parent e30f37376d
commit f160dcf90c

View File

@ -1,4 +1,5 @@
// Copyright (c) 2014 VMware, Inc.
// Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
@ -13,189 +14,181 @@
// under the License.
// this regex matches the hash part of review pages
var hashRegex = /^\#\/c\/[\/\d]+$/
var hashRegex = /^\#\/c\/[\/\d]+$/;
// this regex matches CI comments
var ciRegex = /^(.* CI|Jenkins)$/
var ciRegex = /^(.* CI|Jenkins)$/;
// this regex matches "Patch set #"
var psRegex = /^Patch Set (\d+):/
var psRegex = /^<p>(Uploaded patch set|Patch Set) (\d+)(:|\.)/;
// this regex matches merge failure messages
var mergeFailedRegex = /^Merge Failed\./
var mergeFailedRegex = /Merge Failed\./;
// this regex matches the name of CI systems we trust to report merge failures
var trustedCIRegex = /^(OpenStack CI|Jenkins)$/
var trustedCIRegex = /^(OpenStack CI|Jenkins)$/;
ci_find_comments = function() {
var comments = [];
var last_merge_failure = null;
$("p").each(function() {
var match = psRegex.exec(this.innerHTML);
if (match !== null) {
var psnum = parseInt(match[1]);
var top = $(this).parent().parent().parent();
var name = top.attr("name");
if (!name) {
top = $(this).parent().parent().parent();
name = $(this).parent().prev().children()[0].innerHTML;
}
// Search this comment for results
var comment_object = $(this).parent();
var result_list = [];
comment_object.find("li.comment_test").each(function(i, li) {
var result = {};
result["name"] = $(li).find("span.comment_test_name").find("a")[0].innerHTML;
result["link"] = $(li).find("span.comment_test_name").find("a")[0];
result["result"] = $(li).find("span.comment_test_result")[0];
result_list.push(result);
});
var comment = {"name":name, "psnum":psnum, "top":top,
"merge_failure":null,
"results":result_list,
"comment":comment_object};
comments.push(comment);
// Keep a pointer to the most recent merge failure message from
// the trusted CI system. If there is a message from the system
// after it, drop the reference. This way we end up with a pointer
// iff the last comment from the trusted system is a merge failure.
if (trustedCIRegex.exec(name) !== null) {
if ($(this).next().length>0 &&
mergeFailedRegex.exec($(this).next()[0].innerHTML) !== null) {
last_merge_failure = comment;
} else if (result_list.length>0) {
last_merge_failure = null;
}
}
}
});
// If the last comment from the trusted system is a merge failure,
// mark that comment as a merge failure so it is displayed. (We
// want to ignore it if there was a merge failure that was
// superceded.)
if (last_merge_failure !== null) {
last_merge_failure["merge_failure"] = true;
var ci_parse_psnum = function($panel) {
var match = psRegex.exec($panel.html());
if (match !== null) {
return parseInt(match[2]);
}
return 0;
};
var ci_parse_is_merge_conflict = function($panel) {
return (mergeFailedRegex.exec($panel.html()) !== null);
};
var ci_parse_results = function($panel) {
var result_list = [];
var test_results = $panel.find("li.comment_test");
if (test_results !== null) {
test_results.each(function(i, li) {
var result = {};
result["name"] = $(li).find("span.comment_test_name").find("a")[0].innerHTML;
result["link"] = $(li).find("span.comment_test_name").find("a")[0];
result["result"] = $(li).find("span.comment_test_result")[0];
result_list.push(result);
});
}
return result_list;
};
var ci_parse_comments = function() {
var comments = [];
$(".commentPanel").each(function() {
var comment = {};
comment.name = $(this).attr("name");
comment.email = $(this).attr("email");
comment.date = $(this).find(".commentPanelDateCell").attr("title");
var comment_panel = $(this).find(".commentPanelMessage");
comment.psnum = ci_parse_psnum(comment_panel);
comment.merge_conflict = ci_parse_is_merge_conflict(comment_panel);
comment.results = ci_parse_results(comment_panel);
comment.is_ci = (ciRegex.exec(comment.name) !== null);
comment.is_trusted_ci = (trustedCIRegex.exec(comment.name) !== null);
comment.ref = this;
comments.push(comment);
});
return comments;
};
ci_update_table = function() {
var patchsets = [];
var ci_latest_patchset = function(comments) {
var psnum = 0;
for (var i = 0; i < comments.length; i++) {
psnum = Math.max(psnum, comments[i].psnum);
}
return psnum;
};
var comments = ci_find_comments();
$.each(comments, function(comment_index, comment) {
while (patchsets.length < comment["psnum"]) {
// Whether there is a current merge failure in this
// patchset.
patchsets.push({"_merge_failure": false});
}
// If this comment has results
if (comment["results"].length > 0) {
// Get the name of the system
var name = comment["name"];
// an item in patchsets is a hash of systems
var systems = patchsets[comment["psnum"]-1];
var system;
// Get or create the system object for this system
if (name in systems) {
system = systems[name];
} else {
// A system object has an ordered list of jobs (so
// we preserve what was in the comments), and a
// hash of results (so later runs of the same job
// on the same patchset override previous results).
system = {"jobs": [], "results": {}};
systems[name] = system;
var ci_is_merge_conflict = function(comments) {
var latest = ci_latest_patchset(comments);
var conflict = false;
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
// only if we are actually talking about the latest patch set
if (comment.psnum == latest) {
if (comment.is_trusted_ci) {
conflict = comment.merge_conflict;
}
$.each(comment["results"], function(i, result) {
// For each result, add the name of the job to the
// ordered list if it isn't there already
if (system["jobs"].indexOf(result["name"]) < 0) {
system["jobs"].push(result["name"]);
}
// Then set or override the result
system["results"][result["name"]] = result;
});
}
// The merge failure flag will only be set on a comment if it
// is the most recent comment and is a merge failure.
if (comment["merge_failure"] === true) {
patchsets[comment["psnum"]-1]["_merge_failure"] = true;
}
});
}
return conflict;
};
if (patchsets.length > 0) {
// Create a table and insert it after the approval table
var table = $("table.test_result_table")[0];
if (!table) {
table = document.createElement("table");
$(table).addClass("test_result_table");
$(table).addClass("infoTable").css({"margin-top":"1em", "margin-bottom":"1em"});
var approval_table = $("div.approvalTable");
if (approval_table.length) {
var outer_table = document.createElement("table");
$(outer_table).insertBefore(approval_table);
var outer_table_row = document.createElement("tr");
$(outer_table).append(outer_table_row);
var td = document.createElement("td");
$(outer_table_row).append(td);
$(td).css({"vertical-align":"top"});
$(td).append(approval_table);
td = document.createElement("td");
$(outer_table_row).append(td);
$(td).css({"vertical-align":"top"});
$(td).append(table);
} else {
var big_table_row = $("div.screen>div>div>table>tbody>tr");
var td = $(big_table_row).children()[1];
$(td).append(table);
}
// Hide existing comments
ci_toggle_visibility(comments);
var ci_prepare_results_table = function() {
// Create a table and insert it after the approval table
var table = $("table.test_result_table")[0];
if (!table) {
table = document.createElement("table");
$(table).addClass("test_result_table");
$(table).addClass("infoTable").css({"margin-top":"1em", "margin-bottom":"1em"});
var approval_table = $("div.approvalTable");
if (approval_table.length) {
var outer_table = document.createElement("table");
$(outer_table).insertBefore(approval_table);
var outer_table_row = document.createElement("tr");
$(outer_table).append(outer_table_row);
var td = document.createElement("td");
$(outer_table_row).append(td);
$(td).css({"vertical-align":"top"});
$(td).append(approval_table);
td = document.createElement("td");
$(outer_table_row).append(td);
$(td).css({"vertical-align":"top"});
$(td).append(table);
} else {
$(table).empty();
var big_table_row = $("div.screen>div>div>table>tbody>tr");
var td = $(big_table_row).children()[1];
$(td).append(table);
}
var patchset = patchsets[patchsets.length-1];
if (!patchset["_merge_failure"]) {
$.each(patchset, function(name, system) {
if (name != "_merge_failure") {
// Add a header for each system
var header = $("<tr>").append($('<td class="header" colspan="2">'+name+'</td>'));
$(table).append(header);
// Add the results
$.each(system["jobs"], function(i, name) {
var result = system["results"][name];
var tr = $("<tr>");
tr.append($("<td>").append($(result["link"]).clone()));
tr.append($("<td>").append($(result["result"]).clone()));
$(table).append(tr);
});
}
});
} else {
$(table).empty();
}
return table;
};
var ci_display_results = function(comments) {
var table = ci_prepare_results_table();
if (ci_is_merge_conflict(comments)) {
var mc_header = $("<tr>").append($('<td class="merge_conflict" colpsan="2">Patch in Merge Conflict</td>'));
mc_header.css('width', '400');
mc_header.css('font-weight', 'bold');
mc_header.css('color', 'red');
mc_header.css('padding-left', '2em');
$(table).append(mc_header);
return;
}
var current = ci_latest_patchset(comments);
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
if ((comment.psnum == current) && comment.is_ci && (comment.results.length > 0)) {
var header = $("<tr>").append($('<td class="header">' + comment.name + '</td>'));
header.append('<td class="header ci_date">' + comment.date + '</td>');
$(table).append(header);
for (var j = 0; j < comment.results.length; j++) {
var result = comment.results[j];
var tr = $("<tr>");
tr.append($("<td>").append($(result["link"]).clone()));
tr.append($("<td>").append($(result["result"]).clone()));
$(table).append(tr);
}
}
}
};
ci_page_loaded = function() {
var ci_toggle_visibility = function(comments) {
if (!comments) {
comments = ci_parse_comments();
}
$.each(comments, function(i, comment) {
if (comment.is_ci) {
$(comment.ref).toggle();
}
});
};
var ci_hide_ci_comments = function(comments) {
if (!comments) {
comments = ci_parse_comments();
}
$.each(comments, function(i, comment) {
if (comment.is_ci) {
$(comment.ref).hide();
}
});
};
var ci_page_loaded = function() {
if (hashRegex.test(window.location.hash)) {
$("#toggleci").show();
ci_update_table();
var comments = ci_parse_comments();
ci_display_results(comments);
ci_hide_ci_comments(comments);
} else {
$("#toggleci").hide();
}
};
ci_toggle_visibility = function(comments) {
if (!comments) {
comments = ci_find_comments();
}
$.each(comments, function(i, comment) {
if (ciRegex.exec(comment["name"]) &&
!comment["merge_failure"]) {
comment["top"].toggle();
}
});
};
window.onload = function() {
var input = document.createElement("input");