285 строки
7.0 KiB
JavaScript
285 строки
7.0 KiB
JavaScript
let crashesFile, crashes;
|
|
let options = {
|
|
channel: {
|
|
value: null,
|
|
type: "option",
|
|
},
|
|
crashesType: {
|
|
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 getVersion() {
|
|
return Number(
|
|
crashes.versions[0].substring(0, crashes.versions[0].indexOf("."))
|
|
);
|
|
}
|
|
|
|
function getFixedIn(bug) {
|
|
let version = getVersion();
|
|
|
|
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(signature, obj) {
|
|
let table = document.getElementById("table");
|
|
|
|
let row = table.insertRow(table.rows.length);
|
|
|
|
let rank = row.insertCell(0);
|
|
rank.appendChild(document.createTextNode(obj.tc_rank));
|
|
|
|
let key = row.insertCell(1);
|
|
|
|
let startupImage = document.createElement("img");
|
|
startupImage.title = (obj.startup_percent * 100).toFixed(2) + " %";
|
|
startupImage.src = "rocket_fly.png";
|
|
startupImage.width = 64 * obj.startup_percent;
|
|
startupImage.height = 64 * obj.startup_percent;
|
|
startupImage.style.paddingRight = 5;
|
|
key.appendChild(startupImage);
|
|
|
|
let signatureDiv = document.createElement("div");
|
|
signatureDiv.className = "tooltip";
|
|
|
|
let signatureLink = document.createElement("a");
|
|
signatureLink.appendChild(document.createTextNode(signature));
|
|
signatureLink.href =
|
|
"https://crash-stats.mozilla.org/signature/?date=<%3D" +
|
|
crashes.end_date +
|
|
"&date=>%3D" +
|
|
crashes.start_date +
|
|
"&product=Firefox&" +
|
|
crashes.versions.map((version) => "version=" + version).join("&") +
|
|
"&signature=" +
|
|
signature;
|
|
signatureDiv.appendChild(signatureLink);
|
|
|
|
key.appendChild(signatureDiv);
|
|
|
|
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 bugs = row.insertCell(2);
|
|
obj.bugs
|
|
.sort(
|
|
(bug1, bug2) =>
|
|
new Date(bug2.last_change_time) - new Date(bug1.last_change_time)
|
|
)
|
|
.forEach(function (bug) {
|
|
let fixedIn = getFixedIn(bug);
|
|
if (fixedIn.length == 0) {
|
|
return;
|
|
}
|
|
|
|
let bugLink = document.createElement("a");
|
|
bugLink.appendChild(
|
|
document.createTextNode(
|
|
bug.id +
|
|
" - " +
|
|
"Fixed in " +
|
|
fixedIn.join(", ") +
|
|
", '" +
|
|
bug["cf_status_firefox" + getVersion()] +
|
|
"' in " +
|
|
getVersion() +
|
|
"."
|
|
)
|
|
);
|
|
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";
|
|
}
|
|
|
|
bugs.appendChild(bugLink);
|
|
bugs.appendChild(document.createElement("br"));
|
|
});
|
|
}
|
|
|
|
function buildTable() {
|
|
let file = getOption("channel");
|
|
if (getOption("crashesType") === "All crashes") {
|
|
file += ".json";
|
|
} else if (getOption("crashesType") === "Startup crashes") {
|
|
file += "-startup.json";
|
|
}
|
|
|
|
let promise;
|
|
if (file === crashesFile) {
|
|
promise = Promise.resolve();
|
|
} else {
|
|
promise = fetch(file)
|
|
.then(function (response) {
|
|
return response.json();
|
|
})
|
|
.then(function (val) {
|
|
crashes = val;
|
|
});
|
|
|
|
crashesFile = file;
|
|
}
|
|
|
|
promise
|
|
.then(function () {
|
|
// Order signatures by rank change or kairo's explosiveness.
|
|
Object.keys(crashes.signatures)
|
|
.sort(
|
|
(signature1, signature2) =>
|
|
crashes.signatures[signature1].tc_rank -
|
|
crashes.signatures[signature2].tc_rank
|
|
)
|
|
.filter(
|
|
(signature) =>
|
|
crashes.signatures[signature].bugs.filter(
|
|
(bug) => getFixedIn(bug).length > 0
|
|
).length > 0
|
|
)
|
|
.forEach(function (signature) {
|
|
addRow(signature, crashes.signatures[signature]);
|
|
});
|
|
})
|
|
.catch(function (err) {
|
|
console.error(err);
|
|
});
|
|
}
|
|
|
|
function rebuildTable() {
|
|
while (table.rows.length > 1) {
|
|
table.deleteRow(table.rows.length - 1);
|
|
}
|
|
|
|
buildTable();
|
|
}
|
|
|
|
onLoad
|
|
.then(function () {
|
|
Object.keys(options).forEach(function (optionName) {
|
|
let optionType = getOptionType(optionName);
|
|
let elem = document.getElementById(optionName);
|
|
|
|
if (optionType === "select") {
|
|
setOption(optionName, elem.checked);
|
|
|
|
elem.onchange = function () {
|
|
setOption(optionName, elem.checked);
|
|
rebuildTable();
|
|
};
|
|
} else if (optionType === "option") {
|
|
setOption(optionName, elem.options[elem.selectedIndex].value);
|
|
|
|
elem.onchange = function () {
|
|
setOption(optionName, elem.options[elem.selectedIndex].value);
|
|
rebuildTable();
|
|
};
|
|
} else if (optionType === "button") {
|
|
setOption(optionName, elem.value);
|
|
|
|
document.getElementById(optionName + "Button").onclick = function () {
|
|
setOption(optionName, elem.value);
|
|
rebuildTable();
|
|
};
|
|
} else {
|
|
throw new Error("Unexpected option type.");
|
|
}
|
|
});
|
|
})
|
|
.then(function () {
|
|
buildTable();
|
|
})
|
|
.catch(function (err) {
|
|
console.error(err);
|
|
});
|