- // ==UserScript==
- // @name Firefox for desktop - list modified bugs in Mercurial as sortable table
- // @namespace darkred
- // @version 5.5.9.2
- // @date 2020.8.25
- // @description Lists (as sortable table) bugs related to Firefox for desktop for which patches have landed in Mozilla Mercurial pushlogs
- // @author darkred, johnp
- // @license MIT
- // @include /^https?:\/\/hg\.mozilla\.org.*pushloghtml.*/
- // @grant GM_getResourceURL
- // @grant GM_getResourceText
- // @grant GM_addStyle
- // @grant GM_xmlhttpRequest
- // @require https://code.jquery.com/jquery-2.1.4.min.js
- // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.24.3/js/jquery.tablesorter.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.4.1/moment-timezone-with-data.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.6/jstz.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/datejs/1.0/date.min.js
- // @require https://cdnjs.cloudflare.com/ajax/libs/keypress/2.1.3/keypress.min.js
- // @resource jqUI_CSS http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css
- // @resource IconSet1 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png
- // @resource IconSet2 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-bg_glass_85_dfeffc_1x400.png
- // @resource IconSet3 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
- // @resource IconSet4 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
- // @resource IconSet5 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-icons_217bc0_256x240.png
- // @resource IconSet6 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-icons_469bdd_256x240.png
- // @resource IconSet7 https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/themes/redmond/images/ui-icons_6da8d5_256x240.png
- // Thanks a lot to: johnp (your contribution is most appreciated!), wOxxOm and Brock Adams.
- // @supportURL https://github.com/darkred/Userscripts/issues
- // ==/UserScript==
-
-
- /* eslint-disable no-console, complexity */
- /* global jstz, moment */
-
-
- var silent = false;
- var debug = false;
-
- time('MozillaMercurial');
-
-
-
-
-
- // CSS rules in order to show 'up' and 'down' arrows in each table header
- var stylesheet = `
- <style>
- thead th {
- background-repeat: no-repeat;
- background-position: right center;
- }
- thead th.up {
- padding-right: 20px;
- background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);
- }
- thead th.down {
- padding-right: 20px;
- background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);
- }
- </style>`;
-
- $('head').append(stylesheet);
-
-
- var stylesheet2 =
- `<style>
-
- /* in order to highlight hovered table row */
- #tbl tr:hover{ background:#F6E6C6 !important;}
-
- /* in order the table headers to be larger and bold */
- #tbl th {text-align: -moz-center !important; font-size: larger; font-weight: bold; }
-
- /* in order to remove unnecessairy space between rows */
- #dialog > div > table > tbody {line-height: 14px;}
-
-
- #tbl > thead > tr > th {border-bottom: solid 1px};}
-
-
- #tbl td:nth-child(1) {text-align: -moz-right;}
-
- /* in order the 'product/component' to be aligned to the right */
- #tbl td:nth-child(2) {text-align: -moz-right;}
-
- /* in order the bug list to have width 1500px // it was 1500 and then 1600 */
- .ui-dialog {
- width:1700px !important;
- }
-
- </style>`;
- $('head').append(stylesheet2);
-
-
-
-
-
-
-
- // the dialog will only be opened after all these promises have finished
- var requests = [];
-
-
- // theme for the jQuery dialog
- if (typeof(GM_getResourceText) !== 'undefined' && typeof(GM_addStyle) !== 'undefined') {
-
-
- // https://stackoverflow.com/a/11532646/ , i.e. https://stackoverflow.com/a/11532646/3231411 (By Brock Adams)
- // Themes files URLs: https://cdnjs.com/libraries/jqueryui
- let iconSet1 = GM_getResourceURL ('IconSet1');
- let iconSet2 = GM_getResourceURL ('IconSet2');
- let iconSet3 = GM_getResourceURL ('IconSet3');
- let iconSet4 = GM_getResourceURL ('IconSet4');
- let iconSet5 = GM_getResourceURL ('IconSet5');
- let iconSet6 = GM_getResourceURL ('IconSet6');
- let iconSet7 = GM_getResourceURL ('IconSet7');
- let jqUI_CssSrc = GM_getResourceText ('jqUI_CSS');
- // jqUI_CssSrc = jqUI_CssSrc.replace (/url\(images\/ui\-bg_.*00\.png\)/g, '');
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-bg_glass_75_d0e5f5_1x400\.png/g, iconSet1);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-bg_glass_85_dfeffc_1x400\.png/g, iconSet2);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-bg_gloss-wave_55_5c9ccc_500x100\.png/g, iconSet3);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-bg_inset-hard_100_fcfdfd_1x100\.png/g, iconSet4);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-icons_217bc0_256x240\.png/g, iconSet5);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-icons_469bdd_256x240\.png/g, iconSet6);
- jqUI_CssSrc = jqUI_CssSrc.replace (/images\/ui-icons_6da8d5_256x240\.png/g, iconSet7);
-
- GM_addStyle (jqUI_CssSrc);
-
-
- } else { // e.g. Greasemonkey: https://github.com/greasemonkey/greasemonkey/issues/2548
- // load jquery-ui css dynamically to bypass Content-Security-Policy restrictions
- let loadCss = $.get('https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css', function(css) {
- $('head').append('<style>' + css + '</style>');
- });
- requests.push(loadCss); // prevent a possible race condition where the dialog is opened before the css is loaded
- }
-
-
-
-
-
- var regex = /^https:\/\/bugzilla\.mozilla\.org\/show_bug\.cgi\?id=(.*)$/;
- var base_url = 'https://bugzilla.mozilla.org/rest/bug?include_fields=id,summary,status,resolution,product,component,op_sys,platform,whiteboard,last_change_time&id=';
- var bugIds = [];
- var bugsComplete = [];
-
- var table = document.getElementsByTagName('table')[0];
- var links = table.getElementsByTagName('a');
- var len = links.length;
- for (let i = 0; i < len; i++) {
- let n = links[i].href.match(regex);
- if (n !== null && n.length > 0) {
- let id = parseInt(n[1]);
- if (bugIds.indexOf(id) === -1) {
- bugIds.push(id);
- }
- }
- }
-
- var numBugs = bugIds.length;
- var counter = 0;
-
- var rest_url = base_url + bugIds.join();
-
-
- String.prototype.escapeHTML = function() {
- var tagsToReplace = {
- '&': '&',
- '<': '<',
- '>': '>'
- };
- return this.replace(/[&<>]/g, function(tag) {
- return tagsToReplace[tag] || tag;
- });
- };
-
-
-
- time('MozillaMercurial-REST');
-
-
-
- GM_xmlhttpRequest({
- method: 'GET',
- url: rest_url,
- onload: function(response) {
-
- var data = JSON.parse(response.responseText);
-
- timeEnd('MozillaMercurial-REST');
- $.each(data.bugs, function(index) {
- let bug = data.bugs[index];
- // process bug (let "shorthands" just to simplify things during refactoring)
- let status = bug.status;
- if (bug.resolution !== '') {status += ' ' + bug.resolution;}
- let product = bug.product;
- let component = bug.component;
- let platform = bug.platform;
- if (platform === 'Unspecified') {
- platform = 'Uns';
- }
- if (bug.op_sys !== '' && bug.op_sys !== 'Unspecified') {
- platform += '/' + bug.op_sys;
- }
- let whiteboard = bug.whiteboard === '' ? '[]' : bug.whiteboard;
- // todo: message???
-
-
-
-
-
- // 2015-11-09T14:40:41Z
- function toRelativeTime(time, zone) {
- var format2 = ('YYYY-MM-DD HH:mm:ss Z');
- return moment(time, format2).tz(zone).fromNow();
- }
-
-
- function getLocalTimezone(){
- var tz = jstz.determine(); // Determines the time zone of the browser client
- return tz.name(); // Returns the name of the time zone eg "Europe/Berlin"
- }
-
-
-
-
- var changetime;
- var localTimezone = getLocalTimezone();
-
- if (bug.last_change_time !== '') {
- var temp = toRelativeTime(bug.last_change_time, localTimezone);
- if (temp.match(/(an?) .*/)) {
- changetime = temp.replace(/an?/, 1);
- } else {
- changetime = temp;
- }
- // changetime
- } else {
- changetime = '';
- }
-
-
-
-
-
-
-
-
- log('----------------------------------------------------------------------------------------------------------------------------------');
- log((index + 1) + '/' + numBugs); // Progression counter
- log('BugNo: ' + bug.id + '\nTitle: ' + bug.summary + '\nStatus: ' + status + '\nProduct: ' + product + '\nComponent: ' + component + '\nPlatform: ' + platform + '\nWhiteboard: ' + whiteboard);
-
- if (isRelevant(bug)) {
- // add html code for this bug
- bugsComplete.push('<tr><td><a href="'
- // + 'https://bugzilla.mozilla.org/show_bug.cgi?id='+ bug.id + '">'
- + 'https://bugzilla.mozilla.org/show_bug.cgi?id='+ bug.id + '"' + ' title="' + bug.id + ' - ' + bug.summary + '">#'
- + bug.id
- + '</a></td>'
- + '<td nowrap>(' + product + ': ' + component + ') </td>'
- + '<td>'+bug.summary.escapeHTML() + ' [' + platform + ']' + whiteboard.escapeHTML() + '</td>'
- + '<td>' + changetime + '</td>'
- + '<td>' + status + '</td></tr>'); // previously had a <br> at the end;
- }
- counter++; // increase counter
- // remove processed bug from bugIds
- let i = bugIds.indexOf(bug.id);
- if (i !== -1) {bugIds[i] = null;}
- });
- log('==============\nReceived ' + counter + ' of ' + numBugs + ' bugs.');
-
-
-
-
- // process remaining bugs one-by-one
- time('MozillaMercurial-missing');
- $.each(bugIds, function(index) {
- let id = bugIds[index];
- if (id !== null) {
- time('Requesting missing bug ' + id);
- let promise = $.getJSON('https://bugzilla.mozilla.org/rest/bug/' + id,
- function(json) {
- // I've not end up here yet, so cry if we do
- console.error('Request for bug ' + id + ' succeeded unexpectedly!');
- timeEnd('Requesting missing bug ' + id);
- console.error(json);
- });
- // Actually, we usually get an '401 Authorization Required' error
- promise.fail(function(req, status, error) {
- timeEnd('Requesting missing bug ' + id);
- if (error === 'Authorization Required') {
-
- // log("Bug " + id + " requires authorization!");
- log('https://bugzilla.mozilla.org/show_bug.cgi?id=' + id + ' requires authorization!');
- let text = ' requires authorization!<br>';
-
- bugsComplete.push('<a href="'
- + 'https://bugzilla.mozilla.org/show_bug.cgi?id='+ id + '">#'
- + id + '</a>' + text);
- } else {
- console.error('Unexpected error encountered (Bug' + id + '): ' + status + ' ' + error);
- }
- });
- requests.push(promise);
- }
- });
- // wait for all requests to be settled, then join them together
- // Source: https://stackoverflow.com/questions/19177087/deferred-how-to-detect-when-every-promise-has-been-executed
- $.when.apply($, $.map(requests, function(p) {
- return p.then(null, function() {
- return $.Deferred().resolveWith(this, arguments);
- });
- })).always(function() {
- timeEnd('MozillaMercurial-missing');
- // Variable that will contain all values of the bugsComplete array, and will be displayed in the 'dialog' below
- var docu = '';
- docu = bugsComplete.join('');
- docu = '<table id="tbl" style="width:100%">' +
- '<thead>' +
- '<tr><th>BugNo</th>' +
- '<th>Product/Component</th>' +
- '<th>Summary</th>' +
- '<th>Modified___</th>' +
- '<th>Status____________</th></tr>' +
- '</thead>' +
- '<tbody>' + docu + '</tbody></table>';
-
-
-
-
- var div = document.createElement('div');
- $('div.page_footer').append(div);
- div.id = 'dialog';
- docu = '<div id="dialog_content">' + docu + '</div>';
- div.innerHTML = docu;
- $('#dialog').hide();
-
- $(function() {
- $('#dialog').dialog({
- title: 'List of modified bugs of Firefox for desktop (' + bugsComplete.length + ')',
- width: '1350px'
- });
- });
-
-
-
-
-
-
- // THE CUSTOM PARSER MUST BE PUT BEFORE '$('#tbl').tablesorter ( {'' or else it wont work !!!!
- // add parser through the tablesorter addParser method (for the "Last modified" column)
- $.tablesorter.addParser({
- // set a unique id
- id: 'dates',
- is: function(s) {
- return false; // return false so this parser is not auto detected
- },
- format: function(s) {
- // format your data for normalization
- if (s !== ''){
- var number1, number2;
-
- // format your data for normalization
- number1 = Number((/(.{1,2}) .*/).exec(s)[1]);
-
-
- if (s.match(/A few seconds ago/)) { number2 = 0;}
- else if (s.match(/(.*)seconds?.*/)) { number2 = 1;}
- else if (s.match(/(.*)minutes?.*/)) {number2 = 60;}
- else if (s.match(/(.*)hours?.*/)) { number2 = 3600;}
- else if (s.match(/(.*)days?.*/)) { number2 = 86400;}
- else if (s.match(/(.*)months?.*/)) { number2 = 30 * 86400;}
- else if (s.match(/(.*)years?.*/)) {number2 = 365 * 30 * 86400;}
- return number1 * number2;
-
- }
- },
- // set type, either numeric or text
- type: 'numeric'
- });
-
-
-
- // make table sortable
- $('#tbl').tablesorter({
- cssAsc: 'up',
- cssDesc: 'down',
- sortList: [[3, 0],[1, 0],[2, 0]], // in order the table to be sorted by default by column 3 'Modified', then by column 1 'Product/Component' and then by column 2 'Summary'
- headers: {3: {sorter: 'dates'}},
- initialized: function() {
- var mytable = document.getElementById('tbl');
- for (var i = 2, j = mytable.rows.length + 1; i < j; i++) {
- if (mytable.rows[i].cells[3].innerHTML !== mytable.rows[i - 1].cells[3].innerHTML) {
- for (var k = 0; k < 5; k++) {
- mytable.rows[i - 1].cells[k].style.borderBottom = '1px black dotted';
- }
- }
- }
- }
- });
-
-
-
-
-
-
- log('ALL IS DONE');
- timeEnd('MozillaMercurial');
-
-
-
-
-
- });
-
- }
- });
-
-
- var flag = 1;
-
- // bind keypress of ` so that when pressed, the separators between groups of the same timestamps to be removed, in order to sort manually
- var listener = new window.keypress.Listener();
- listener.simple_combo('`', function() {
- // console.log('You pressed `');
- if (flag === 1) {
- flag = 0;
- // remove seperators
- var mytable = document.getElementById('tbl');
- for (let i = 2, j = mytable.rows.length + 1; i < j; i++) {
- for (let k = 0; k < 5; k++) {
- mytable.rows[i - 1].cells[k].style.borderBottom = 'none';
- }
- }
- var sorting = [[1, 0], [2, 0]]; // sort by column 1 'Product/Component' and then by column 2 'Summary'
- $('#tbl').trigger('sorton', [sorting]);
- } else {
- if (flag === 0) {
- flag = 1;
- // console.log('You pressed ~');
- sorting = [[3, 0], [1, 0], [2, 0]]; // sort by column 3 'Modified Date, then by '1 'Product/Component' and then by column 2 'Summary'
- $('#tbl').trigger('sorton', [sorting]);
- mytable = document.getElementById('tbl');
- for (let i = 2, j = mytable.rows.length + 1; i < j; i++) {
- if (mytable.rows[i].cells[3].innerHTML !== mytable.rows[i - 1].cells[3].innerHTML) {
- for (let k = 0; k < 5; k++) {
- mytable.rows[i - 1].cells[k].style.borderBottom = '1px black dotted';
- }
- }
- }
- }
- }
- });
-
-
-
-
-
-
- function isRelevant(bug) {
- if (!bug.id) {return false;}
- // if (bug.status && bug.status !== 'RESOLVED' && bug.status !== 'VERIFIED') {
- // log(' IRRELEVANT because of it\'s Status --> ' + bug.status);
- // return false;
- // }
- if (bug.component && bug.product && bug.component === 'Build Config' && (bug.product === 'Toolkit' || bug.product === 'Firefox')) {
- log(' IRRELEVANT because of it\'s Product --> ' + bug.product + 'having component --> ' + bug.component);
- return false;
- }
- if (bug.product &&
- bug.product !== 'Add-on SDK' &&
- bug.product !== 'Cloud Services' &&
- bug.product !== 'Core' &&
- bug.product !== 'Firefox' &&
- bug.product !== 'Hello (Loop)' &&
- bug.product !== 'Toolkit') {
- log(' IRRELEVANT because of it\'s Product --> ' + bug.product);
- return false;
- }
- if (bug.component &&
- bug.component === 'AutoConfig' ||
- bug.component === 'Build Config' ||
- bug.component === 'DMD' ||
- bug.component === 'Embedding: GRE Core' ||
- bug.component === 'Embedding: Mac' ||
- bug.component === 'Embedding: MFC Embed' ||
- bug.component === 'Embedding: Packaging' ||
- bug.component === 'Hardware Abstraction Layer' ||
- bug.component === 'mach' ||
- bug.component === 'Nanojit' ||
- bug.component === 'QuickLaunch' ||
- bug.component === 'Widget: Gonk') {
- log(' IRRELEVANT because of it\'s Component --> ' + bug.component);
- return false;
- }
-
- log(' OK ' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=' + bug.id);
- return true;
- }
-
-
-
-
- function log(str) {
- if (!silent) {
- console.log(str);
- }
- }
-
- function time(str) {
- if (debug) {
- console.time(str);
- }
- }
-
- function timeEnd(str) {
- if (debug) {
- console.timeEnd(str);
- }
- }
-
- $('#dialog').dialog({
- modal: false,
- title: 'Draggable, sizeable dialog',
- position: {
- my: 'top',
- at: 'top',
- of: document,
- collision: 'none'
- },
- // width: 1500, // not working
- zIndex: 3666
- })
- .dialog('widget').draggable('option', 'containment', 'none');
-
- //-- Fix crazy bug in FF! ...
- $('#dialog').parent().css({
- position: 'fixed',
- top: 0,
- left: '4em',
- width: '75ex'
- });