beta-stability-pushlog.js (322 lines of code) (raw):

let options = { beta1: { value: null, type: "option", }, beta2: { value: null, type: "option", }, product: { value: null, type: "option", }, }; 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 dateToStr(date) { let month = "" + (date.getMonth() + 1); let day = "" + date.getDate(); let year = date.getFullYear(); if (month.length < 2) { month = "0" + month; } if (day.length < 2) { day = "0" + day; } return year + "-" + month + "-" + day; } function addDays(date, days) { let result = new Date(date); result.setDate(result.getDate() + days); let today = new Date(); if (result > today) { return today; } else { return result; } } function getBaseVersion(version) { return version.substring(0, version.indexOf(".0b")); } function getReleaseDate(version, release_history) { if (version.endsWith("b99") && !(version in release_history)) { // XXX: Assume release date is really close to latest beta build. Remove this // hack when https://bugzilla.mozilla.org/show_bug.cgi?id=1192197 is fixed. let maxDate = new Date(0); for (let release of Object.entries(release_history).filter((r) => r[0].startsWith(getBaseVersion(version)) )) { let date = new Date(release[1]); if (date > maxDate) { maxDate = date; } } return maxDate; } return new Date(release_history[version]); } function getTag(version) { if (version.endsWith("b99")) { return "FIREFOX_RELEASE_" + getBaseVersion(version) + "_BASE"; } return "FIREFOX_" + version.replace(".", "_") + "_RELEASE"; } function getComparison() { if (!getOption("beta1") || !getOption("beta2")) { return; } while (table.rows.length > 1) { table.deleteRow(table.rows.length - 1); } let url = new URL(location.href); url.search = "?product=" + getOption("product") + "&beta1=" + getOption("beta1") + "&beta2=" + getOption("beta2"); history.replaceState({}, document.title, url.href); fetch( "https://product-details.mozilla.org/1.0/firefox_history_development_releases.json" ) .then((response) => response.json()) .then((release_history) => { let date1 = getReleaseDate(getOption("beta1"), release_history); let date2 = getReleaseDate(getOption("beta2"), release_history); let endDate1 = addDays(date1, 7); let endDate2 = addDays(date2, 7); document.getElementById("dates").innerHTML = getOption("beta1") + " released on " + dateToStr(date1) + " (crashes from " + dateToStr(date1) + " to " + dateToStr(endDate1) + ")<br>" + getOption("beta2") + " released on " + dateToStr(date2) + " (crashes from " + dateToStr(date2) + " to " + dateToStr(endDate2) + ")"; let fromchange = getTag(getOption("beta1")); let tochange = getTag(getOption("beta2")); fetch( "https://hg.mozilla.org/releases/mozilla-beta/pushloghtml?fromchange=" + fromchange + "&tochange=" + tochange ) .then((response) => response.text()) .then((data) => { let bugs = new Set(); let regex = /Bug ([0-9]+)/gi; let res; while ((res = regex.exec(data)) !== null) { bugs.add(res[1]); } let table = document.getElementById("table"); bugs.forEach((bug) => fetch( "https://bugzilla.mozilla.org/rest/bug/" + bug + "?include_fields=product,component,cf_crash_signature" ) .then((response) => response.json()) .then((data) => { // Skip bugs with no signatures. if ( "bugs" in data && data["bugs"][0]["cf_crash_signature"] == "" ) { return; } // Skip bugs that are not related to the current product. if ( "bugs" in data && getOption("product") === "Firefox" && (data["bugs"][0]["product"] === "Firefox for Android" || data["bugs"][0]["component"] === "WebExtensions: Android") ) { return; } // Skip bugs where the cf_crash_signature field is not defined. if ( "bugs" in data && !("cf_crash_signature" in data["bugs"][0]) ) { return; } let row = table.insertRow(table.rows.length); let bugElem = row.insertCell(0); let aElem = document.createElement("a"); aElem.href = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug; aElem.textContent = bug; bugElem.appendChild(aElem); let signaturesCell = row.insertCell(1); let evolution = row.insertCell(2); let result = document.createElement("span"); if (!("bugs" in data)) { result.style.color = "maroon"; result.textContent = "Not accessible."; } else { let signatures = data["bugs"][0]["cf_crash_signature"]; signatures = signatures.replace(/\[@ /g, "[@"); signatures = signatures.replace(/\[@/g, ""); signatures = signatures.replace(/ ]\r\n/g, "\\"); signatures = signatures.replace(/]\r\n/g, "\\"); signatures = signatures.replace("]", ""); signatures = signatures.split("\\"); for (let signature of signatures) { let signatureElem = document.createElement("a"); signatureElem.href = "https://crash-stats.mozilla.org/signature/?signature=" + signature; signatureElem.textContent = signature; signaturesCell.appendChild(signatureElem); signaturesCell.appendChild(document.createElement("br")); } let query1 = fetch( "https://crash-stats.mozilla.org/api/SuperSearch/?product=" + getOption("product") + "&_results_number=0&_facets_size=0&version=" + getOption("beta1") + "&date=>%3D" + dateToStr(date1) + "&date=<%3D" + dateToStr(endDate1) + "&signature=%3D" + signatures.join("&signature=%3D") ).then((response) => response.json()); let query2 = fetch( "https://crash-stats.mozilla.org/api/SuperSearch/?product=" + getOption("product") + "&_results_number=0&_facets_size=0&version=" + getOption("beta2") + "&date=>%3D" + dateToStr(date2) + "&date=<%3D" + dateToStr(endDate2) + "&signature=%3D" + signatures.join("&signature=%3D") ).then((response) => response.json()); Promise.all([query1, query2]).then((data) => { result.textContent = data[0]["total"] + " before; " + data[1]["total"] + " after."; }); } evolution.appendChild(result); }) ); }); }); } let curBeta; onLoad .then(() => fetch("https://product-details.mozilla.org/1.0/firefox_versions.json") ) .then((response) => response.json()) .then((result) => { let betaVersion = result["LATEST_FIREFOX_DEVEL_VERSION"]; curBeta = betaVersion.substring(0, betaVersion.indexOf(".")); }) .then(() => fetch( "https://product-details.mozilla.org/1.0/firefox_history_development_releases.json" ) ) .then((response) => response.json()) .then((data) => { let betas1 = document.getElementById("beta1"); let betas2 = document.getElementById("beta2"); let versions = Object.keys(data).filter((version) => version.startsWith(curBeta) ); console.log(versions); if (versions.length <= 1) { let warning = "Need at least two beta builds in order to compare."; if (versions.length == 1) { warning += " Currently only " + version + " is available."; } document.getElementById("dates").innerHTML = '<p style="font-weight: bold; color: red;">' + warning + "</p>"; throw new Error("Need at least two beta builds in order to compare."); } for (let i = 0; i < versions.length; i++) { let version = versions[i]; var opt = document.createElement("option"); opt.value = version; opt.textContent = version; if (i != versions.length - 1) { betas1.appendChild(opt); } if (i != 0) { betas2.appendChild(opt.cloneNode(true)); } } betas1.selectedIndex = betas1.options.length - 1; betas2.selectedIndex = betas2.options.length - 1; }) .then(function () { 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); } 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); }; } else if (optionType === "button") { if (getOption(optionName)) { elem.value = getOption(optionName); } setOption(optionName, elem.value.trim()); } else { throw new Error("Unexpected option type."); } document.getElementById("compareButton").onclick = function () { getComparison(); }; }); if (queryVars.length >= 2) { getComparison(); } }) .catch(function (err) { console.error(err); });