stab-crashes/scomp.js

336 строки
13 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
var gDebug, gLog;
var gAnalysisPath = "../../";
var gBzBasePath = "https://bugzilla.mozilla.org/";
var gSocorroPath = "https://crash-stats.mozilla.com/";
var gSearchBase, gSearch1, gSearch2, gLimit = 20, gFetchLimit = 300;
var gSigData = {}, gSocorroAPIToken, gBugInfo = {};
window.onload = function() {
// Socorro API token is not required, so the implementation in this code was never finished.
gSocorroAPIToken = getParameterByName("token");
// Fetch Super Search common parameters and specific parameters for query 1 and 2.
searchCommon = getParameterByName("common");
searchParam1 = getParameterByName("p1");
searchParam2 = getParameterByName("p2");
// Fetch limits from parameters.
var limit = getParameterByName("limit");
if (limit.match(/^(\d+)+$/) && (limit >= 3) && (limit <= 1000)) {
gLimit = limit;
}
var flimit = getParameterByName("fetchlimit");
if (flimit.match(/^(\d+)+$/) && (flimit >= 3) && (flimit <= 1000)) {
gFetchLimit = flimit;
}
gSearch1 = searchCommon + (searchParam1 ? "&" + searchParam1 : "");
gSearch2 = searchCommon + (searchParam2 ? "&" + searchParam2 : "");
gSearchBase = searchCommon;
document.getElementById("paramCommon").value = searchCommon;
document.getElementById("paramS1").value = searchParam1;
document.getElementById("paramS2").value = searchParam2;
document.getElementById("search1link").href =
gSocorroPath + "search/?" + gSearch1;
document.getElementById("search2link").href =
gSocorroPath + "search/?" + gSearch2;
if (gSearch1 && gSearch2 && (gSearch1 != gSearch2)) {
processData();
}
else {
document.getElementById("scompParams").classList.remove("hidden");
document.getElementById("scompResult").classList.add("hidden");
}
document.getElementById("searchParamToggle").onclick = function() {
var paramArea = document.getElementById("scompParams");
if (paramArea.classList.contains("hidden")) {
paramArea.classList.remove("hidden");
}
else {
paramArea.classList.add("hidden");
}
return false;
}
}
function processData() {
var tblBody = document.getElementById("scompTBody");
displayMessage("Requesting data for first search…");
// Only return the signature facet, no "normal" results (crash IDs).
fetchFile(gSocorroPath + "api/SuperSearch/?_facets=signature&_results_number=0" +
"&" + gSearch1 + "&_facets_size=" + gFetchLimit, "json",
function(aData1) {
if (aData1) {
var result1 = aData1.facets.signature;
var total1 = aData1.total;
displayMessage("Requesting data for second search…");
fetchFile(gSocorroPath + "api/SuperSearch/?_facets=signature&_results_number=0" +
"&" + gSearch2 + "&_facets_size=" + gFetchLimit, "json",
function(aData2) {
if (aData2) {
var result2 = aData2.facets.signature;
var total2 = aData2.total;
displayMessage("Processing " + result1.length + " and " + result2.length + " results for searches…");
// Assemble data into a form we can use.
for (var i = 0; i <= result1.length - 1; i++) {
gSigData[result1[i].term] = {
"count1": result1[i].count,
"pct1": 100 * result1[i].count / total1,
"signature": result1[i].term,
"count2": 0,
"pct2": 0,
};
}
// Assemble data into a form we can use.
for (var i = 0; i <= result2.length - 1; i++) {
if (!gSigData[result2[i].term]) {
gSigData[result2[i].term] = {
"count1": 0,
"pct1": 0,
"signature": result2[i].term,
}
}
gSigData[result2[i].term].count2 = result2[i].count;
gSigData[result2[i].term].pct2 = 100 * result2[i].count / total2;
}
// Calculate the differences.
for (var signature in gSigData) {
gSigData[signature].pctcmp = gSigData[signature].pct2 - gSigData[signature].pct1;
}
setTimeout(function() {
buildDataTable();
fetchBugs();
}, 0);
}
else {
displayMessage("ERROR - couldn't fetch search #2!");
}
}
);
}
else {
displayMessage("ERROR - couldn't fetch search #1!");
}
}
);
}
function fetchBugs() {
for (var signature in gSigData) {
gSigData[signature].bugs = [];
if (document.getElementById("sdata_" + encodeURIComponent(signature))) {
// Only actually fetch if this is actually shown.
fetchBugsForSignature(signature);
}
}
}
function fetchBugsForSignature(aSignature) {
gSigData[aSignature].bugs = [];
fetchFile(gSocorroPath + "api/Bugs/?signatures=" + encodeURIComponent(aSignature), "json",
function(aSignature, aData) {
if (aData) {
for (var i = 0; i <= aData.hits.length - 1; i++) {
if (aData.hits[i].signature == aSignature) {
gSigData[aSignature].bugs.push(aData.hits[i].id);
}
}
buildBugsField(aSignature);
}
else {
console.log("ERROR - couldn't find bug data for " + aSignature + "!");
}
}.bind(undefined, aSignature) // Prepend signature to the argument list.
);
}
function buildDataTable() {
var msgRow = document.getElementById("message_row");
if (msgRow) {
msgRow.parentNode.removeChild(msgRow);
}
// Header
var trow = document.getElementById("scompTHeader")
.appendChild(document.createElement("tr"));
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "#";
cell.setAttribute("title", "Rank");
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "Signature";
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "Bugs";
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "#1";
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "#2";
var cell = trow.appendChild(document.createElement("th"));
cell.textContent = "+/-";
// Body
var tblBody = document.getElementById("scompTBody");
var sigSorted = Object.keys(gSigData).sort(
function (a, b) { return Math.abs(gSigData[b].pctcmp) - Math.abs(gSigData[a].pctcmp); }
);
var listNum = Math.min(gLimit, sigSorted.length);
for (var i = 0; i <= listNum - 1; i++) {
signature = sigSorted[i];
var trow = tblBody.appendChild(document.createElement("tr"));
trow.setAttribute("id", "sdata_" + encodeURIComponent(signature));
var cell = trow.appendChild(document.createElement("td"));
cell.textContent = i + 1;
cell.classList.add("rank");
var cell = trow.appendChild(document.createElement("td"));
cell.classList.add("sig");
var link = cell.appendChild(document.createElement("a"));
link.setAttribute("href",
gSocorroPath + "signature?" + gSearchBase +
"&signature=" + encodeURIComponent(signature));
link.textContent = signature;
var cell = trow.appendChild(document.createElement("td"));
cell.classList.add("bugs");
var cell = trow.appendChild(document.createElement("td"));
cell.classList.add("pct");
cell.textContent = gSigData[signature].pct1.toFixed(1) + "%";
cell.setAttribute("title", gSigData[signature].count1);
if (gSigData[signature].count1 == 0) {
cell.classList.add("zero");
}
var cell = trow.appendChild(document.createElement("td"));
cell.classList.add("pct");
cell.textContent = gSigData[signature].pct2.toFixed(1) + "%";
cell.setAttribute("title", gSigData[signature].count2);
if (gSigData[signature].count2 == 0) {
cell.classList.add("zero");
}
var cell = trow.appendChild(document.createElement("td"));
cell.classList.add("pctcmp");
if (gSigData[signature].pctcmp > 0) {
cell.classList.add("plus");
}
else if (gSigData[signature].pctcmp < 0) {
cell.classList.add("minus");
}
cell.textContent = (gSigData[signature].pctcmp > 0 ? "+" : "") +
gSigData[signature].pctcmp.toFixed(1) + "%";
}
}
function buildBugsField(aSignature) {
var sigRow = document.getElementById("sdata_" + encodeURIComponent(aSignature));
var bugsField = sigRow.querySelector(".bugs");
for (var i = 0; i <= gSigData[aSignature].bugs.length - 1; i++) {
if (i > 0) { // Add spaces when we have multiple bugs.
bugsField.appendChild(document.createTextNode(" "));
}
var link = bugsField.appendChild(document.createElement("a"));
link.dataset["bugid"] = gSigData[aSignature].bugs[i];
link.setAttribute("href",
gBzBasePath + "show_bug.cgi?id=" + gSigData[aSignature].bugs[i]);
link.textContent = gSigData[aSignature].bugs[i];
// Add Bugzilla data.
if (gBugInfo[gSigData[aSignature].bugs[i]]) {
beautifyBugzillaLink(link);
}
else {
fetchFile(gBzBasePath + "rest/bug/" + gSigData[aSignature].bugs[i] + "?include_fields=id,summary,status,resolution", "json",
function(aLink, aData) {
if (aData && aData.bugs && aData.bugs.length) {
gBugInfo[aData.bugs[0].id] = aData.bugs[0];
beautifyBugzillaLink(aLink);
}
else if (aData && aData.error) {
// On error, create fake bug info.
gBugInfo[aLink.dataset["bugid"]] =
{status: "ERROR", resolution: "", summary: aData.message};
beautifyBugzillaLink(aLink);
}
else {
console.log("ERROR - couldn't find info for bug " + aLink.dataset["bugid"] + "!");
}
}.bind(undefined, link), // Prepend link to the argument list.
true // Accept 401 responses and still return them as JSON.
);
}
}
}
function beautifyBugzillaLink(aLink) {
if (gBugInfo[aLink.dataset["bugid"]]) {
aLink.dataset["status"] = gBugInfo[aLink.dataset["bugid"]].status;
aLink.dataset["resolution"] = gBugInfo[aLink.dataset["bugid"]].resolution;
aLink.title = gBugInfo[aLink.dataset["bugid"]].status + " " +
gBugInfo[aLink.dataset["bugid"]].resolution + " - " +
gBugInfo[aLink.dataset["bugid"]].summary;
}
else {
console.log("ERROR - info for bug " + aLink.dataset["bugid"] + " should exist but doesn't!");
}
}
function displayMessage(aErrorMessage) {
var msgRow = document.getElementById("message_row");
if (msgRow) {
msgRow.parentNode.removeChild(msgRow);
}
var trow = document.getElementById("scompTBody")
.appendChild(document.createElement('tr'));
trow.setAttribute("id", "message_row");
var cell = trow.appendChild(document.createElement('td'));
cell.textContent = aErrorMessage;
return cell;
}
function fetchFile(aURL, aFormat, aCallback, aAccept401) {
var XHR = new XMLHttpRequest();
XHR.onreadystatechange = function() {
if (XHR.readyState == 4) {/*
gLog.appendChild(document.createElement("li"))
.appendChild(document.createTextNode(aURL + " - " + XHR.status +
" " + XHR.statusText));*/
}
if (XHR.readyState == 4 && (XHR.status == 200 || (XHR.status == 401 && aAccept401))) {
// so far so good
if (XHR.responseXML != null && aFormat == "xml" &&
XHR.responseXML.getElementById('test').firstChild.data)
aCallback(aXHR.responseXML.getElementById('test').firstChild.data);
else if (XHR.responseText != null && aFormat == "json")
aCallback(JSON.parse(XHR.responseText));
else
aCallback(XHR.responseText);
} else if (XHR.readyState == 4 && XHR.status != 200) {
// fetched the wrong page or network error...
console.log("ERROR: XHR status " + XHR.status + " - " + aURL);
aCallback(null);
}
};
XHR.open("GET", aURL);
if (gSocorroAPIToken) {
// XXX: Should work but doesn't yet! We'll need to figure this out.
// Use this path when we have a token so bug 1143424 can be tested.
XHR.setRequestHeader("Auth-Token", gSocorroAPIToken);
}
if (aFormat == "json") { XHR.setRequestHeader("Accept", "application/json"); }
else if (aFormat == "xml") { XHR.setRequestHeader("Accept", "application/xml"); }
try {
XHR.send();
}
catch (e) {
console.log("ERROR: XHR send - " + e + " - " + aURL);
aCallback(null);
}
}
function getParameterByName(aName) {
// from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
name = aName.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}