compare-betas.js (334 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); return result; } function getBaseVersion(version) { return version.substring(0, version.indexOf(".0b")); } function getReleaseDate(version, build_id, release_history) { if (build_id) { // XXX: Assume release date is the build ID. Remove this // hack when https://bugzilla.mozilla.org/show_bug.cgi?id=1192197 is fixed. return new Date( build_id.substring(0, 4) + "-" + build_id.substring(4, 6) + "-" + build_id.substring(6, 8) ); } if (!(version in release_history)) { return new Date(); } return new Date(release_history[version]); } function getComparison() { if (!getOption("beta1") || !getOption("beta2")) { return; } let version1 = getOption("beta1"); let version2 = getOption("beta2"); let build_id1 = ""; let build_id2 = ""; if (version1.includes(" - ")) { let vals = version1.split(" - "); version1 = vals[0]; build_id1 = vals[1]; } if (version2.includes(" - ")) { let vals = version2.split(" - "); version2 = vals[0]; build_id2 = vals[1]; } fetch( "https://product-details.mozilla.org/1.0/firefox_history_development_releases.json" ) .then((response) => response.json()) .then((release_history) => { let date1 = dateToStr( getReleaseDate(version1, build_id1, release_history) ); let date2 = dateToStr( getReleaseDate(version2, build_id2, release_history) ); let url = new URL(location.href); url.search = "?product=" + getOption("product") + "&beta1=" + getOption("beta1") + "&beta2=" + getOption("beta2"); history.replaceState({}, document.title, url.href); let signatureNumberElem = document.getElementById("signatureNumber"); let signatureNumber = Number(signatureNumberElem.value); if (!signatureNumber || isNaN(signatureNumber)) { signatureNumber = 20; } document.getElementById("frame").src = "scomp.html?limit=" + signatureNumber + "&common=product%3D" + getOption("product") + "&p1=version%3D" + version1 + (build_id1 ? "%26build_id=" + build_id1 : "") + "%26date%3D%3E" + date1 + "&p2=version%3D" + version2 + (build_id2 ? "%26build_id=" + build_id2 : "") + "%26date%3D%3E" + date2; let total1, total2; Promise.all([ fetch( "https://crash-stats.mozilla.org/api/SuperSearch/?product=" + getOption("product") + "&version=" + version1 + (build_id1 ? "&build_id=" + build_id1 : "") + "&_results_number=0&_facets_size=0" + "&date=>%3D" + date1 ) .then((response) => response.json()) .then((results) => (total1 = results["total"] || 0)), fetch( "https://crash-stats.mozilla.org/api/SuperSearch/?product=" + getOption("product") + "&version=" + version2 + (build_id2 ? "&build_id=" + build_id2 : "") + "&_results_number=0&_facets_size=0" + "&date=>%3D" + date2 ) .then((response) => response.json()) .then((results) => (total2 = results["total"] || 0)), ]).then(() => { let warning = ""; if (total1 < total2 * 0.2) { warning = "WARNING: Number of crash reports for " + getOption("beta1") + " (" + total1 + ") are way lower than for " + getOption("beta2") + " (" + total2 + "); the comparison might be skewed."; } else if (total2 < total1 * 0.2) { warning = "WARNING: Number of crash reports for " + getOption("beta2") + " (" + total2 + ") are way lower than for " + getOption("beta1") + " (" + total1 + "); the comparison might be skewed."; } document.getElementById("warning").textContent = warning; }); }); } function compareBuildIDs(build_id1, build_id2) { let year1 = Number(build_id1.substring(0, 4)); let year2 = Number(build_id2.substring(0, 4)); if (year1 > year2) { return 1; } else if (year1 < year2) { return -1; } let month1 = Number(build_id1.substring(4, 6)); let month2 = Number(build_id2.substring(4, 6)); if (month1 > month2) { return 1; } else if (month1 < month2) { return -1; } let day1 = Number(build_id1.substring(6, 8)); let day2 = Number(build_id2.substring(6, 8)); if (day1 > day2) { return 1; } else if (day1 < day2) { return -1; } return 0; } function compareVersions(versionA, versionB) { let majorA = Number(versionA.substring(0, versionA.indexOf("."))); let majorB = Number(versionB.substring(0, versionB.indexOf("."))); if (majorA > majorB) { return 1; } else if (majorA < majorB) { return -1; } let minorA; let minorB; if (!versionA.includes(" - ")) { minorA = Number(versionA.substring(versionA.indexOf("b") + 1)); } else { minorA = Number( versionA.substring(versionA.indexOf("b") + 1, versionA.indexOf(" ")) ); } if (!versionB.includes(" - ")) { minorB = Number(versionB.substring(versionB.indexOf("b") + 1)); } else { minorB = Number( versionB.substring(versionB.indexOf("b") + 1, versionB.indexOf(" ")) ); } if (minorA > minorB) { return 1; } else if (minorA < minorB) { return -1; } let buildIDA = versionA.substring(versionA.indexOf(" - ") + 3); let buildIDB = versionB.substring(versionB.indexOf(" - ") + 3); return compareBuildIDs(buildIDA, buildIDB); } onLoad .then(() => fetch( "https://product-details.mozilla.org/1.0/firefox_history_development_releases.json" ) ) .then((response) => response.json()) .then((data) => { let versions = Object.keys(data); let rc = versions.find((version) => version.endsWith("b99")); if (rc) { return fetch( "https://crash-stats.mozilla.org/api/SuperSearch/?version=" + rc + "&product=Firefox&_facets=build_id&_results_number=0" ) .then((response) => response.json()) .then((data) => { return data["facets"]["build_id"] .map((elem) => rc + " - " + elem["term"]) .concat(versions.filter((version) => !version.endsWith("b99"))); }); } else { return versions; } }) .then((versions) => { return versions.sort(compareVersions); }) .then((versions) => { // Only consider the latest 10 builds. if (versions.length > 10) { versions = versions.slice(versions.length - 10); } return versions; }) .then((versions) => { let betas1 = document.getElementById("beta1"); let betas2 = document.getElementById("beta2"); for (let i = 0; i < versions.length; i++) { let version = versions[i]; var opt = document.createElement("option"); opt.value = 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 === decodeURIComponent(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); });