all_missing_uplifts.js (369 lines of code) (raw):

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));