396 строки
13 KiB
JavaScript
396 строки
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.org/";
|
|
|
|
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, " "));
|
|
}
|