Firefox for desktop - list modified bugs in Mercurial - inbound

It generates lists of bugs related to Firefox for desktop for which patches have landed in Mozilla Mercurial pushlogs

目前为 2016-01-01 提交的版本。查看 最新版本

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