stab-crashes/all_missing_uplifts.js

427 строки
11 KiB
JavaScript

let crashesFile, crashes;
let options = {
channel: {
value: null,
type: "option",
},
wontfix: {
value: null,
type: "select",
},
};
function getOption(name) {
return options[name].value;
}
function getOptionType(name) {
return options[name].type;
}
function setOption(name, value) {
return (options[name].value = value);
}
let onLoad = new Promise(function (resolve, reject) {
window.onload = resolve;
});
function agoString(val, str) {
return val + " " + (val == 1 ? str : str + "s") + " ago";
}
function prettyDate(date) {
date = new Date(date);
let today = new Date();
let hoursDiff = Math.round((today.getTime() - date.getTime()) / 3600000);
if (hoursDiff < 24) {
return agoString(hoursDiff, "hour");
}
let daysDiff = Math.round((today.getTime() - date.getTime()) / 86400000);
if (daysDiff < 10) {
return agoString(daysDiff, "day");
}
let weeksDiff = Math.round(
(today.getTime() - date.getTime()) / (7 * 86400000)
);
if (weeksDiff < 3) {
return agoString(weeksDiff, "week");
}
let monthsDiff =
today.getMonth() +
12 * today.getFullYear() -
(date.getMonth() + 12 * date.getFullYear());
if (monthsDiff < 12) {
return agoString(monthsDiff, "month");
}
return agoString(today.getFullYear() - date.getFullYear(), "year");
}
function fetchWithRetry(url, trials = 0) {
return fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Error while getting " + url);
} else {
return response;
}
})
.catch((error) => {
let timeout = Math.pow(2, trials) * 1000;
if (timeout > 32000) {
timeout = 32000;
}
if (trials > 64) {
throw error;
}
return new Promise(function (resolve, reject) {
setTimeout(() => {
fetchWithRetry(url, trials + 1).then(resolve, reject);
}, timeout);
});
});
}
async function getVersion(channel) {
let response = await fetchWithRetry(
"https://product-details.mozilla.org/1.0/firefox_versions.json"
);
let data = await response.json();
if (channel == "beta") {
return data["LATEST_FIREFOX_DEVEL_VERSION"];
} else if (channel == "release") {
return data["LATEST_FIREFOX_VERSION"];
}
throw new Error("Unknown channel!");
}
function getMajor(version) {
return Number(version.substring(0, version.indexOf(".")));
}
function getFixedIn(bug, version) {
let statuses = ["", "---", "?", "fix-optional", "affected"];
if (getOption("wontfix")) {
statuses.push("wontfix");
}
if (!statuses.includes(bug["cf_status_firefox" + version])) {
return [];
}
let versionEnd = version;
if (getOption("channel") == "beta") {
versionEnd += 2;
} else if (getOption("channel") == "release") {
versionEnd += 3;
}
let fixedIn = [];
for (version += 1; version <= versionEnd; version++) {
if (
bug["cf_status_firefox" + version] === "fixed" ||
bug["cf_status_firefox" + version] === "verified"
) {
fixedIn.push(version);
}
}
return fixedIn;
}
function addRow(bug, version) {
let table = document.getElementById("table");
let row = table.insertRow(table.rows.length);
let today = new Date();
let three_days_ago = new Date().setDate(today.getDate() - 3);
let ten_days_ago = new Date().setDate(today.getDate() - 10);
let bug_elem = row.insertCell(0);
let fixedIn = getFixedIn(bug, version);
let bugLink = document.createElement("a");
bugLink.appendChild(
document.createTextNode(
bug.id +
" - " +
"Fixed in " +
fixedIn.join(", ") +
", '" +
bug["cf_status_firefox" + version] +
"' in " +
version +
"."
)
);
bugLink.title =
(bug.resolution ? bug.resolution + " - " : "") +
"Last activity: " +
prettyDate(bug.last_change_time);
bugLink.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug.id;
let bugDate = new Date(bug.last_change_time);
if (bugDate > three_days_ago) {
bugLink.style.color = "green";
} else if (bugDate > ten_days_ago) {
bugLink.style.color = "orange";
} else {
bugLink.style.color = "red";
}
bug_elem.appendChild(bugLink);
let signatures_elem = row.insertCell(1);
bug["signatures"].forEach((signature) => {
let signature_link = document.createElement("a");
signature_link.appendChild(document.createTextNode(signature));
signature_link.href =
"https://crash-stats.mozilla.org/signature/?signature=" +
encodeURIComponent(signature);
signatures_elem.appendChild(signature_link);
signatures_elem.appendChild(document.createElement("br"));
});
let crashes_count = row.insertCell(2);
crashes_count.appendChild(document.createTextNode(bug["crashes_count"]));
}
function buildTable() {
return getVersion(getOption("channel")).then((full_version) => {
let version = getMajor(full_version);
let versionEnd = version;
if (getOption("channel") == "beta") {
versionEnd += 1;
} else if (getOption("channel") == "release") {
versionEnd += 2;
}
let query =
"https://bugzilla.mozilla.org/rest/bug?f1=cf_crash_signature&o1=isnotempty&";
let fieldNum = 2;
query += "j" + fieldNum + "=AND&f" + fieldNum + "=OP&";
fieldNum++;
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
version +
"&o" +
fieldNum +
"=notequals&v" +
fieldNum +
"=fixed&";
fieldNum++;
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
version +
"&o" +
fieldNum +
"=notequals&v" +
fieldNum +
"=verified&";
fieldNum++;
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
version +
"&o" +
fieldNum +
"=notequals&v" +
fieldNum +
"=unaffected&";
fieldNum++;
if (!getOption("wontfix")) {
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
version +
"&o" +
fieldNum +
"=notequals&v" +
fieldNum +
"=wontfix&";
fieldNum++;
}
query += "f" + fieldNum + "=CP&";
fieldNum++;
query += "j" + fieldNum + "=OR&f" + fieldNum + "=OP&";
fieldNum++;
for (v = version + 1; v <= versionEnd; v++) {
query += "j" + fieldNum + "=OR&f" + fieldNum + "=OP&";
fieldNum++;
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
v +
"&o" +
fieldNum +
"=equals&v" +
fieldNum +
"=verified&";
fieldNum++;
query +=
"f" +
fieldNum +
"=cf_status_firefox" +
v +
"&o" +
fieldNum +
"=equals&v" +
fieldNum +
"=fixed&";
fieldNum++;
query += "f" + fieldNum + "=CP&";
fieldNum++;
}
query += "f" + fieldNum + "=CP&";
fieldNum++;
query += "include_fields=id,last_change_time,cf_crash_signature";
for (v = version; v <= versionEnd; v++) {
query += ",cf_status_firefox" + v;
}
return fetchWithRetry(query)
.then((response) => response.json())
.then((data) => data["bugs"])
.then((bugs) =>
Promise.all(
bugs.map((bug) => {
let signatures = bug["cf_crash_signature"]
.split(/\s*]\s*/)
.map((signature) => signature.substring(2).trim());
signatures = signatures.filter((signature) => signature != "");
let count = 0;
return Promise.all(
signatures.map((signature) =>
fetchWithRetry(
"https://crash-stats.mozilla.org/api/SuperSearch/?version=" +
full_version +
"&signature=%3D" +
encodeURIComponent(signature) +
"&product=Firefox&_results_number=0&_facets_size=0"
)
.then((response) => response.json())
.then((result) => {
count += result["total"];
})
)
).then(() => {
bug["signatures"] = signatures;
bug["crashes_count"] = count;
return bug;
});
})
)
)
.then((bugs) => bugs.filter((bug) => bug["crashes_count"] > 0))
.then((bugs) =>
bugs.sort((a, b) => b["crashes_count"] - a["crashes_count"])
)
.then((bugs) => bugs.forEach((bug) => addRow(bug, version)));
});
}
function startSpinner() {
document.getElementById("spin").style.display = "";
document.getElementById("table").style.display = "none";
}
function stopSpinner() {
document.getElementById("spin").style.display = "none";
document.getElementById("table").style.display = "";
}
function reloadPage() {
let url = new URL(location.href);
url.search = "?channel=" + getOption("channel");
window.location = url;
}
onLoad
.then(() => startSpinner())
.then(() => {
let queryVars = new URL(location.href).search.substring(1).split("&");
Object.keys(options).forEach(function (optionName) {
let optionType = getOptionType(optionName);
let elem = document.getElementById(optionName);
for (let queryVar of queryVars) {
if (queryVar.startsWith(optionName + "=")) {
let option = queryVar.substring((optionName + "=").length).trim();
setOption(optionName, option);
}
}
if (optionType === "select") {
if (getOption(optionName)) {
elem.checked = getOption(optionName);
}
setOption(optionName, elem.checked);
elem.onchange = function () {
setOption(optionName, elem.checked);
reloadPage();
};
} else if (optionType === "option") {
if (getOption(optionName)) {
for (let i = 0; i < elem.options.length; i++) {
if (elem.options[i].value === getOption(optionName)) {
elem.selectedIndex = i;
break;
}
}
}
setOption(optionName, elem.options[elem.selectedIndex].value);
elem.onchange = function () {
setOption(optionName, elem.options[elem.selectedIndex].value);
reloadPage();
};
} else if (optionType === "button") {
if (getOption(optionName)) {
elem.value = getOption(optionName);
}
setOption(optionName, elem.value);
document.getElementById(optionName + "Button").onclick = function () {
setOption(optionName, elem.value);
reloadPage();
};
} else {
throw new Error("Unexpected option type.");
}
});
})
.then(() => buildTable())
.then(() => stopSpinner())
.catch((err) => console.error(err));