Firefox for desktop - list modified bugs in Mercurial as sortable table

It generates a sortable table list of bugs related to Firefox for desktop for which patches have landed in Mozilla Mercurial pushlogs

当前为 2017-11-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Firefox for desktop - list modified bugs in Mercurial as sortable table
  3. // @namespace darkred
  4. // @authors darkred, johnp
  5. // @license MIT
  6. // @description It generates a sortable table list of bugs related to Firefox for desktop for which patches have landed in Mozilla Mercurial pushlogs
  7. // @version 5.5.5
  8. // @date 2017.11.16
  9. // @include /^https?:\/\/hg\.mozilla\.org.*pushloghtml.*/
  10. // @grant none
  11. // @require https://code.jquery.com/jquery-2.1.4.min.js
  12. // @require https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
  13. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.24.3/js/jquery.tablesorter.min.js
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.4.1/moment-timezone-with-data.min.js
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.6/jstz.min.js
  17. // @require https://cdnjs.cloudflare.com/ajax/libs/datejs/1.0/date.min.js
  18. // @require https://cdnjs.cloudflare.com/ajax/libs/keypress/2.1.3/keypress.min.js
  19. // ==/UserScript==
  20.  
  21.  
  22. // CSS rules in order to show 'up' and 'down' arrows in each table header
  23. var stylesheet = ' \
  24. <style> \
  25. thead th { \
  26. background-repeat: no-repeat; \
  27. background-position: right center; \
  28. } \
  29. thead th.up { \
  30. padding-right: 20px; \
  31. background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); \
  32. } \
  33. thead th.down { \
  34. padding-right: 20px; \
  35. background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); \
  36. } \
  37. } \
  38. </style>';
  39.  
  40. $('head').append(stylesheet);
  41.  
  42.  
  43. var stylesheet2 =
  44. `<style>
  45.  
  46. /* in order to highlight hovered table row */
  47. #tbl tr:hover{ background:#F6E6C6 !important;}
  48.  
  49. /* in order the table headers to be larger and bold */
  50. #tbl th {text-align: -moz-center !important; font-size: larger; font-weight: bold; }
  51.  
  52. /* in order to remove unnecessairy space between rows */
  53. #dialog > div > table > tbody {line-height: 14px;}
  54.  
  55.  
  56. #tbl > thead > tr > th {border-bottom: solid 1px};}
  57.  
  58.  
  59. #tbl td:nth-child(1) {text-align: -moz-right;}
  60.  
  61. /* in order the 'product/component' to be aligned to the right */
  62. #tbl td:nth-child(2) {text-align: -moz-right;}
  63.  
  64. /* in order the bug list to have width 1500px // it was 1500 and then 1600 */
  65. .ui-dialog {
  66. width:1700px !important;
  67. }
  68.  
  69. </style>`;
  70. $('head').append(stylesheet2);
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79. var silent = false;
  80. var debug = false;
  81.  
  82. time('MozillaMercurial');
  83.  
  84. String.prototype.escapeHTML = function() {
  85. var tagsToReplace = {
  86. '&': '&amp;',
  87. '<': '&lt;',
  88. '>': '&gt;'
  89. };
  90. return this.replace(/[&<>]/g, function(tag) {
  91. return tagsToReplace[tag] || tag;
  92. });
  93. };
  94.  
  95. // theme for the jQuery dialog
  96. // $('head').append(
  97. // '<link ' +
  98. // 'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css" ' +
  99. // // 'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.min.css" ' + // uncomment this line (and comment line 89) in order to change theme
  100. // 'rel="stylesheet" type="text/css">'
  101. // );
  102. // var newCSS = GM_getResourceText ('customCSS');
  103. // GM_addStyle (newCSS);
  104. $.ajax({
  105. url:'http://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css',
  106. success:function(data){
  107. $('<style></style>').appendTo('head').html(data);
  108. }
  109. });
  110.  
  111.  
  112. var regex = /^https:\/\/bugzilla\.mozilla\.org\/show_bug\.cgi\?id=(.*)$/;
  113. 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=';
  114. var bugIds = [];
  115. var bugsComplete = [];
  116.  
  117. var table = document.getElementsByTagName('table')[0];
  118. var links = table.getElementsByTagName('a');
  119. var len = links.length;
  120. for (let i = 0; i < len; i++) {
  121. let n = links[i].href.match(regex);
  122. if (n !== null && n.length > 0) {
  123. let id = parseInt(n[1]);
  124. if (bugIds.indexOf(id) === -1) {
  125. bugIds.push(id);
  126. }
  127. }
  128. }
  129.  
  130. var numBugs = bugIds.length;
  131. var counter = 0;
  132.  
  133. var rest_url = base_url + bugIds.join();
  134. time('MozillaMercurial-REST');
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142. $.getJSON(rest_url, function(data) {
  143. timeEnd('MozillaMercurial-REST');
  144. $.each(data.bugs, function(index) {
  145. let bug = data.bugs[index];
  146. // process bug (let "shorthands" just to simplify things during refactoring)
  147. let status = bug.status;
  148. if (bug.resolution !== '') {status += ' ' + bug.resolution;}
  149. let product = bug.product;
  150. let component = bug.component;
  151. let platform = bug.platform;
  152. if (platform == 'Unspecified') {
  153. platform = 'Uns';
  154. }
  155. if (bug.op_sys !== '' && bug.op_sys !== 'Unspecified') {
  156. platform += '/' + bug.op_sys;
  157. }
  158. let whiteboard = bug.whiteboard === '' ? '[]' : bug.whiteboard;
  159. // todo: message???
  160.  
  161.  
  162.  
  163.  
  164.  
  165. // 2015-11-09T14:40:41Z
  166. function toRelativeTime(time, zone) {
  167. var format2 = ('YYYY-MM-DD HH:mm:ss Z');
  168. return moment(time, format2).tz(zone).fromNow();
  169. }
  170.  
  171.  
  172. function getLocalTimezone(){
  173. var tz = jstz.determine(); // Determines the time zone of the browser client
  174. return tz.name(); // Returns the name of the time zone eg "Europe/Berlin"
  175. }
  176.  
  177.  
  178.  
  179.  
  180. var changetime;
  181. var localTimezone = getLocalTimezone();
  182.  
  183. if (bug.last_change_time !== '') {
  184. var temp = toRelativeTime(bug.last_change_time, localTimezone);
  185. if (temp.match(/(an?)\ .*/)) {
  186. changetime = temp.replace(/an?/, 1);
  187. } else {
  188. changetime = temp;
  189. }
  190. // changetime
  191. } else {
  192. changetime = '';
  193. }
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202. log('----------------------------------------------------------------------------------------------------------------------------------');
  203. log((index + 1) + '/' + numBugs); // Progression counter
  204. log('BugNo: ' + bug.id + '\nTitle: ' + bug.summary + '\nStatus: ' + status + '\nProduct: ' + product + '\nComponent: ' + component + '\nPlatform: ' + platform + '\nWhiteboard: ' + whiteboard);
  205.  
  206. if (isRelevant(bug)) {
  207. // add html code for this bug
  208. bugsComplete.push('<tr><td><a href="'
  209. + 'https://bugzilla.mozilla.org/show_bug.cgi?id='+ bug.id + '">'
  210. + bug.id
  211. + '</a></td>'
  212. + '<td nowrap>(' + product + ': ' + component + ') </td>'
  213. + '<td>'+bug.summary.escapeHTML() + ' [' + platform + ']' + whiteboard.escapeHTML() + '</td>'
  214. + '<td>' + changetime + '</td>'
  215. + '<td>' + status + '</td></tr>'); // previously had a <br> at the end;
  216. }
  217. counter++; // increase counter
  218. // remove processed bug from bugIds
  219. let i = bugIds.indexOf(bug.id);
  220. if (i !== -1) {bugIds[i] = null;}
  221. });
  222. log('==============\nReceived ' + counter + ' of ' + numBugs + ' bugs.');
  223.  
  224.  
  225.  
  226.  
  227. // process remaining bugs one-by-one
  228. var requests = [];
  229. time('MozillaMercurial-missing');
  230. $.each(bugIds, function(index) {
  231. let id = bugIds[index];
  232. if (id !== null) {
  233. time('Requesting missing bug ' + id);
  234. let promise = $.getJSON('https://bugzilla.mozilla.org/rest/bug/' + id,
  235. function(json) {
  236. // I've not end up here yet, so cry if we do
  237. console.error('Request succeeded unexpectedly!');
  238. console.error('Please submit this information to the script authors:');
  239. timeEnd('Requesting missing bug ' + id);
  240. console.log(json);
  241. let bug = json.bugs[0];
  242. console.log(bug);
  243. // TODO: display as much information as possible
  244. });
  245. // Actually, we usually get an error
  246. promise.error(function(req, status, error) {
  247. timeEnd('Requesting missing bug ' + id);
  248. if (error == 'Authorization Required') {
  249. // log("Bug " + id + " requires authorization!");
  250. log('https://bugzilla.mozilla.org/show_bug.cgi?id=' + id + ' requires authorization!');
  251. let text = ' requires authorization!<br>';
  252.  
  253. bugsComplete.push('<a href="'
  254. + 'https://bugzilla.mozilla.org/show_bug.cgi?id='+ id + '">#'
  255. + id + '</a>' + text);
  256. } else {
  257. console.error('Unexpected error encountered (Bug' + id + '): ' + status + ' ' + error);
  258. }
  259. });
  260. requests.push(promise);
  261. }
  262. });
  263. // wait for all requests to be settled, then join them together
  264. // Source: https://stackoverflow.com/questions/19177087/deferred-how-to-detect-when-every-promise-has-been-executed
  265. $.when.apply($, $.map(requests, function(p) {
  266. return p.then(null, function() {
  267. return $.Deferred().resolveWith(this, arguments);
  268. });
  269. })).always(function() {
  270. timeEnd('MozillaMercurial-missing');
  271. // Variable that will contain all values of the bugsComplete array, and will be displayed in the 'dialog' below
  272. var docu = '';
  273. docu = bugsComplete.join('');
  274. docu = ' <table id="tbl" style="width:100%">' +
  275. '<thead>' +
  276. '<tr><th>BugNo</th>' +
  277. '<th>Product/Component</th>' +
  278. '<th>Summary</th>' +
  279. '<th>Modified___</th>' +
  280. '<th>Status____________</th></tr>' +
  281. '</thead>' +
  282. '<tbody>' + docu + '</tbody></table>';
  283.  
  284.  
  285.  
  286.  
  287. var div = document.createElement('div');
  288. $('div.page_footer').append(div);
  289. div.id = 'dialog';
  290. docu = '<div id="dialog_content" title="Relevant Bugs">' + docu + '</div>';
  291. div.innerHTML = docu;
  292. $('#dialog').hide();
  293.  
  294. $(function() {
  295. $('#dialog').dialog({
  296. title: 'List of modified bugs of Firefox for desktop (' + bugsComplete.length + ')',
  297. width: '1350px'
  298. });
  299. });
  300.  
  301.  
  302.  
  303.  
  304.  
  305.  
  306. // THE CUSTOM PARSER MUST BE PUT BEFORE '$('#tbl').tablesorter ( {'' or else it wont work !!!!
  307. // add parser through the tablesorter addParser method (for the "Last modified" column)
  308. $.tablesorter.addParser({
  309. // set a unique id
  310. id: 'dates',
  311. is: function(s) {
  312. return false; // return false so this parser is not auto detected
  313. },
  314. format: function(s) {
  315. // format your data for normalization
  316. if (s !== ''){
  317. var number1, number2;
  318.  
  319. // format your data for normalization
  320. number1 = Number((/(.{1,2})\ .*/).exec(s)[1]);
  321.  
  322.  
  323. if (s.match(/A few seconds ago/)) { number2 = 0;}
  324. else if (s.match(/(.*)seconds?.*/)) { number2 = 1;}
  325. else if (s.match(/(.*)minutes?.*/)) {number2 = 60;}
  326. else if (s.match(/(.*)hours?.*/)) { number2 = 3600;}
  327. else if (s.match(/(.*)days?.*/)) { number2 = 86400;}
  328. else if (s.match(/(.*)months?.*/)) { number2 = 30 * 86400;}
  329. else if (s.match(/(.*)years?.*/)) {number2 = 365 * 30 * 86400;}
  330. return number1 * number2;
  331.  
  332. }
  333. },
  334. // set type, either numeric or text
  335. type: 'numeric'
  336. });
  337.  
  338.  
  339.  
  340. // make table sortable
  341. $('#tbl').tablesorter({
  342. cssAsc: 'up',
  343. cssDesc: 'down',
  344. 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'
  345. headers: {3: {sorter: 'dates'}},
  346. initialized: function() {
  347. var mytable = document.getElementById('tbl');
  348. for (var i = 2, j = mytable.rows.length + 1; i < j; i++) {
  349. if (mytable.rows[i].cells[3].innerHTML != mytable.rows[i - 1].cells[3].innerHTML) {
  350. for (var k = 0; k < 5; k++) {
  351. mytable.rows[i - 1].cells[k].style.borderBottom = '1px black dotted';
  352. }
  353. }
  354. }
  355. }
  356. });
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363. log('ALL IS DONE');
  364. timeEnd('MozillaMercurial');
  365.  
  366.  
  367.  
  368.  
  369.  
  370. });
  371.  
  372. });
  373.  
  374.  
  375. var flag = 1;
  376.  
  377. // bind keypress of ` so that when pressed, the separators between groups of the same timestamps to be removed, in order to sort manually
  378. var listener = new window.keypress.Listener();
  379. listener.simple_combo('`', function() {
  380. // console.log('You pressed `');
  381. if (flag === 1) {
  382. flag = 0;
  383. // remove seperators
  384. var mytable = document.getElementById('tbl');
  385. for (var i = 2, j = mytable.rows.length + 1; i < j; i++) {
  386. for (var k = 0; k < 5; k++) {
  387. mytable.rows[i - 1].cells[k].style.borderBottom = 'none';
  388. }
  389. }
  390. var sorting = [[1, 0], [2, 0]]; // sort by column 1 'Product/Component' and then by column 2 'Summary'
  391. $('#tbl').trigger('sorton', [sorting]);
  392. } else {
  393. if (flag === 0) {
  394. flag = 1;
  395. // console.log('You pressed ~');
  396. var sorting = [[3, 0], [1, 0], [2, 0]]; // sort by column 3 'Modified Date, then by '1 'Product/Component' and then by column 2 'Summary'
  397. $('#tbl').trigger('sorton', [sorting]);
  398. var mytable = document.getElementById('tbl');
  399. for (var i = 2, j = mytable.rows.length + 1; i < j; i++) {
  400. if (mytable.rows[i].cells[3].innerHTML != mytable.rows[i - 1].cells[3].innerHTML) {
  401. for (var k = 0; k < 5; k++) {
  402. mytable.rows[i - 1].cells[k].style.borderBottom = '1px black dotted';
  403. }
  404. }
  405. }
  406. }
  407. }
  408. });
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415. function isRelevant(bug) {
  416. if (!bug.id) {return false;}
  417. // if (bug.status && bug.status != 'RESOLVED' && bug.status != 'VERIFIED') {
  418. // log(' IRRELEVANT because of it\'s Status --> ' + bug.status);
  419. // return false;
  420. // }
  421. if (bug.component && bug.product && bug.component == 'Build Config' && (bug.product == 'Toolkit' || bug.product == 'Firefox')) {
  422. log(' IRRELEVANT because of it\'s Product --> ' + bug.product + 'having component --> ' + bug.component);
  423. return false;
  424. }
  425. if (bug.product && bug.product != 'Add-on SDK' &&
  426. bug.product != 'Cloud Services' &&
  427. bug.product != 'Core' &&
  428. bug.product != 'Firefox' &&
  429. bug.product != 'Hello (Loop)' &&
  430. bug.product != 'Toolkit') {
  431. log(' IRRELEVANT because of it\'s Product --> ' + bug.product);
  432. return false;
  433. }
  434. if (bug.component && bug.component == 'AutoConfig' ||
  435. bug.component == 'Build Config' ||
  436. bug.component == 'DMD' ||
  437. bug.component == 'Embedding: GRE Core' ||
  438. bug.component == 'Embedding: Mac' ||
  439. bug.component == 'Embedding: MFC Embed' ||
  440. bug.component == 'Embedding: Packaging' ||
  441. bug.component == 'Hardware Abstraction Layer' ||
  442. bug.component == 'mach' ||
  443. bug.component == 'Nanojit' ||
  444. bug.component == 'QuickLaunch' ||
  445. bug.component == 'Widget: Gonk') {
  446. log(' IRRELEVANT because of it\'s Component --> ' + bug.component);
  447. return false;
  448. }
  449.  
  450. log(' OK ' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=' + bug.id);
  451. return true;
  452. }
  453.  
  454.  
  455.  
  456.  
  457. function log(str) {
  458. if (!silent) {
  459. console.log(str);
  460. }
  461. }
  462.  
  463. function time(str) {
  464. if (debug) {
  465. console.time(str);
  466. }
  467. }
  468.  
  469. function timeEnd(str) {
  470. if (debug) {
  471. console.timeEnd(str);
  472. }
  473. }
  474.  
  475. // $(function() {
  476. $('#dialog').dialog({
  477. modal: false,
  478. title: 'Draggable, sizeable dialog',
  479. position: {
  480. my: 'top',
  481. at: 'top',
  482. of: document,
  483. collision: 'none'
  484. },
  485. // width: 1500, // not working
  486. zIndex: 3666
  487. })
  488. .dialog('widget').draggable('option', 'containment', 'none');
  489.  
  490. //-- Fix crazy bug in FF! ...
  491. $('#dialog').parent().css({
  492. position: 'fixed',
  493. top: 0,
  494. left: '4em',
  495. width: '75ex'
  496. });