Greasy Fork 还支持 简体中文。

Jira Deployment Helper

Adds Jira Code Review button to Subversion Commits tab, highlights database changes in the comments or description that have "dbo." in them or say "ERRORFIXED", "ERRORIGNORED" or "STILLOCCURRING", highlights tasks in list view and dashboard gadgets based on original time estimate and due date.

目前為 2019-04-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Jira Deployment Helper
  3. // @namespace http://www.mapyourshow.com/
  4. // @description Adds Jira Code Review button to Subversion Commits tab, highlights database changes in the comments or description that have "dbo." in them or say "ERRORFIXED", "ERRORIGNORED" or "STILLOCCURRING", highlights tasks in list view and dashboard gadgets based on original time estimate and due date.
  5. // @include */secure/dashboard*
  6. // @include */browse/*
  7. // @include */issues/*
  8. // @version 2.0.0
  9. // @require https://code.jquery.com/jquery-3.4.0.min.js
  10. // ==/UserScript==
  11.  
  12. function scriptLoader(callback) {
  13. var script = document.createElement("script");
  14. script.textContent = "(" + callback.toString() + ")(window.AJS.$);";
  15. document.body.appendChild(script);
  16. }
  17.  
  18. mainCode = function($) {
  19. var version = 0,
  20. task = '',
  21. project = '';
  22.  
  23. function waitForAddedNode(params) {
  24. new MutationObserver(function(mutations) {
  25. var el = document.querySelector(params.id);
  26. if (el) {
  27. //this.disconnect();
  28. params.done(el);
  29. }
  30. }).observe(params.parent || document, {
  31. subtree: !!params.recursive,
  32. childList: true,
  33. });
  34. }
  35.  
  36. waitForAddedNode({
  37. id: 'div.gadget',
  38. parent: document.body,
  39. recursive: true,
  40. done: function(el) {
  41. console.log('here!');
  42. console.log(el);
  43.  
  44. if (document.querySelector('div.gadget') !== null) {
  45. initJiraPlugin(jQuery('div.gadget-inline').contents().find('table'));
  46. } else {
  47. initJiraPlugin(jQuery('iframe').contents().find('table'));
  48. }
  49. }
  50. });
  51.  
  52. // // Select the node that will be observed for mutations
  53. // var targetNode = document.querySelector('div.gadget');
  54.  
  55. // // Options for the observer (which mutations to observe)
  56. // var config = { attributes: true, childList: true };
  57.  
  58. // // Callback function to execute when mutations are observed
  59. // var callback = function(mutationsList) {
  60. // for(var mutation of mutationsList) {
  61. // // if (mutation.type == 'childList') {
  62. // // console.log('A child node has been added or removed.');
  63. // // } else if (mutation.type == 'attributes') {
  64. // // console.log('The ' + mutation.attributeName + ' attribute was modified.');
  65. // // }
  66.  
  67. // if (typeof jQuery('div.gadget-inline') !== 'undefined') {
  68. // initJiraPlugin(jQuery('div.gadget-inline').contents().find('table'));
  69. // } else {
  70. // initJiraPlugin(jQuery('iframe').contents().find('table'));
  71. // }
  72. // }
  73. // };
  74.  
  75. // // Create an observer instance linked to the callback function
  76. // var observer = new MutationObserver(callback);
  77.  
  78. // // Start observing the target node for configured mutations
  79. // observer.observe(targetNode, config);
  80.  
  81. // These are for the Issues page
  82. JIRA.bind(JIRA.Events.NEW_CONTENT_ADDED, function(e, context, reason) {
  83. initJiraPlugin(jQuery('table#issuetable'));
  84. });
  85.  
  86. if (typeof JIRA.ViewIssueTabs !== 'undefined') {
  87. JIRA.ViewIssueTabs.onTabReady(function() {
  88. initJiraPlugin(jQuery('table#issuetable'));
  89. });
  90. }
  91.  
  92. function initJiraPlugin(issueTable) {
  93. version = parseFloat(jQuery('meta[name=ajs-version-number]').attr('content'));
  94. if (jQuery('meta[name=ajs-issue-key]').length) {
  95. task = jQuery('meta[name=ajs-issue-key]').attr('content');
  96. project = task.split('-')[0];
  97. }
  98.  
  99. // This colorizes the tasks in list view based on Original Estimate and Due Date
  100. colorTasks(issueTable, version);
  101.  
  102. // This highlights all instances of database names with "dbo." in them
  103. highlightDB(version);
  104.  
  105. // This highlights all instances of "ERRORFIXED"
  106. highlightErrorNotes(version);
  107.  
  108. // Clears duplicated highlight tags when editing a comment/description with a highlighted term in it
  109. clearInvalidHighlightTags(version);
  110.  
  111. // This adds the Code Review button in the Subversion tab
  112. addCodeReviewButton(version);
  113.  
  114. // Defaults the issues page to "All Issues" instead of "Open Issues"
  115. allIssuesDefault(version);
  116. }
  117.  
  118. function Change(label, url) {
  119. this.label = label;
  120. var s = url.split(url.indexOf('#') > 0 ? '#' : '?r=');
  121. this.ref = s[0];
  122. this.revision = s[1];
  123. this.commits = 1;
  124. this.pref = new RegExp('/[a-zA-Z-_]+/', '');
  125.  
  126. this.asA = function() {
  127. return this.label;
  128. };
  129.  
  130. this.setMaxRevision = function(revision) {
  131. if (revision > this.revision) {
  132. this.revision = revision;
  133. }
  134. this.commits++;
  135. };
  136.  
  137. this.samePrefix = function(prefix) {
  138. return getPrefix() == prefix;
  139. };
  140.  
  141. this.getPrefix = function() {
  142. return this.label.match(this.pref)[0];
  143. };
  144. }
  145.  
  146. function createReviewView() {
  147. var index = 0;
  148. var changes = [];
  149. var mapChanges = [];
  150. //select dev with id issueContent
  151. //select div with id issue_actions_container in it
  152. //select tables in it
  153. //find rows with text 'Files Changed'
  154. //select next sibling tr (in terminology of jquery it is 'next adjacent selector'
  155. //select td in it
  156. //select a in it
  157. jQuery('div#issue_actions_container table tr:contains("Files Changed") + tr td').each(function() {
  158. var lines = jQuery(this).text().split('\n');
  159. jQuery.each(lines, function() {
  160. if (this.trim().substr(0, 7) == '/trunk/' || this.trim().substr(0, 10) == '/branches/') {
  161. var change = new Change(this, this);
  162. if (mapChanges[change.label]) {
  163. mapChanges[change.label].setMaxRevision(change.revision);
  164. } else {
  165. changes[index++] = change;
  166. mapChanges[change.label] = change;
  167. }
  168. }
  169. });
  170. });
  171. changes.sort(function(a, b) {
  172. return (b.label.toLowerCase() < a.label.toLowerCase()) - (a.label.toLowerCase() < b.label.toLowerCase());
  173. });
  174. var res = '<div class="issuePanelContainer" id="issue_actions_container"><table cellpadding="2" cellspacing="0" border="0" width="100%">';
  175. res += '<tbody><tr><td bgcolor="#f0f0f0"><b>Commits</b></td><td bgcolor="#f0f0f0"><b>Changed File</b></td></tr>';
  176. var prefix;
  177. var sep = false;
  178. for (var i = 0; i < index; i++) {
  179. sep = (prefix && (prefix != changes[i].getPrefix()));
  180. res = res + '<tr><td' + style(sep) + '>' + changes[i].commits + '</td><td' + style(sep) + '>' + changes[i].asA() + '</td></tr>';
  181. prefix = changes[i].getPrefix();
  182. }
  183. return res + '</tbody></table></div>';
  184. }
  185.  
  186. function style(sep) {
  187. return sep ? ' style="border-top: solid #BBB 1px;"' : '';
  188. }
  189.  
  190. function highlightDB(version) {
  191. if (jQuery('#description-val').length) {
  192. var $descriptionDiv = jQuery('#description-val');
  193. } else if (jQuery('#issue-description').length) {
  194. var $descriptionDiv = jQuery('#issue-description');
  195. }
  196. if ($descriptionDiv) {
  197. var result = $descriptionDiv.html();
  198. var regex = new RegExp('[\\w.*_]*dbo\\.[\\w]*','ig');
  199. $descriptionDiv.html(result.replace(regex, '<span style="background-color:yellow;">$&</span>'));
  200. }
  201.  
  202. jQuery('.action-body').each(function() {
  203. result = jQuery(this).html();
  204. regex = new RegExp('[\\w.*_]*dbo\\.[\\w]*','ig');
  205. jQuery(this).html(result.replace(regex, '<span style="background-color:yellow;">$&</span>'));
  206. });
  207. }
  208.  
  209. function highlightErrorNotes(version) {
  210. if (jQuery('#description-val').length) {
  211. var $descriptionDiv = jQuery('#description-val');
  212. } else if (jQuery('#issue-description').length) {
  213. var $descriptionDiv = jQuery('#issue-description');
  214. }
  215. if ($descriptionDiv) {
  216. var result = $descriptionDiv.html();
  217. var regex = new RegExp('ERRORFIXED','g');
  218. $descriptionDiv.html(result.replace(regex, '<span style="background-color:#ffff00;">$&</span>'));
  219.  
  220. result = $descriptionDiv.html();
  221. regex = new RegExp('ERRORIGNORED','g');
  222. $descriptionDiv.html(result.replace(regex, '<span style="background-color:#90ee90;">$&</span>'));
  223.  
  224. result = $descriptionDiv.html();
  225. regex = new RegExp('STILLOCCURRING','g');
  226. $descriptionDiv.html(result.replace(regex, '<span style="background-color:#ffa500;">$&</span>'));
  227. }
  228.  
  229. jQuery('.action-body').each(function() {
  230. if (project == 'ERRORS') {
  231. result = jQuery(this).html();
  232. regex = new RegExp('boothsales','gi');
  233. jQuery(this).html(result.replace(regex, '<span style="background-color:#ff0000;">$&</span>'));
  234.  
  235. result = jQuery(this).html();
  236. regex = new RegExp('/checkout/checkout.cfm','gi');
  237. jQuery(this).html(result.replace(regex, '<span style="background-color:#ff0000;">$&</span>'));
  238. }
  239.  
  240. result = jQuery(this).html();
  241. regex = new RegExp('ERRORFIXED','g');
  242. jQuery(this).html(result.replace(regex, '<span style="background-color:#ffff00;">$&</span>'));
  243.  
  244. result = jQuery(this).html();
  245. regex = new RegExp('ERRORIGNORED','g');
  246. jQuery(this).html(result.replace(regex, '<span style="background-color:#90ee90;">$&</span>'));
  247.  
  248. result = jQuery(this).html();
  249. regex = new RegExp('STILLOCCURRING','g');
  250. jQuery(this).html(result.replace(regex, '<span style="background-color:#ffa500;">$&</span>'));
  251. });
  252. }
  253.  
  254. function clearInvalidHighlightTags(version) {
  255. if (jQuery('#description-val').length) {
  256. var $descriptionDiv = jQuery('#description-val');
  257. } else if (jQuery('#issue-description').length) {
  258. var $descriptionDiv = jQuery('#issue-description');
  259. }
  260. if ($descriptionDiv) {
  261. $descriptionDiv.html($descriptionDiv.html().replace(/&lt;span style="background-color:(.*);"&gt;/gi, ''));
  262. $descriptionDiv.html($descriptionDiv.html().replace(/&lt;\/span&gt;/gi, ''));
  263. }
  264.  
  265. jQuery('.action-body').each(function() {
  266. jQuery(this).html(jQuery(this).html().replace(/&lt;span style="background-color:(.*);"&gt;/gi, ''));
  267. jQuery(this).html(jQuery(this).html().replace(/&lt;\/span&gt;/gi, ''));
  268. });
  269. }
  270.  
  271. function addCodeReviewButton(version) {
  272. console.log(version);
  273. if (!jQuery('#review_button_div').length) {
  274. if (version >= 7) {
  275. if (jQuery('li#svnplus-subversion-commits-tabpanel').hasClass('active') == 1) { //where the Subversion Commits is but not inside a tag
  276. jQuery('div#issue_actions_container:first').prepend('<div id="review_button_div" style="width: 100%;margin-bottom:10px;"><button id="review_button">Code Review</button></div>');
  277. var view = createReviewView();
  278. var revView = false;
  279. jQuery('#review_button').click(function() {
  280. var oldView = jQuery('table', 'div#issue_actions_container').detach();
  281. jQuery('div#review_button_div').after(view);
  282. jQuery('#review_button').text(revView ? 'Code Review' : 'All Commits');
  283. view = oldView;
  284. revView = !revView;
  285. });
  286. }
  287. } else if (version >= 6) {
  288. if (jQuery('li#subversion-commits-tabpanel').hasClass('active') == 1) { //where the Subversion Commits is but not inside a tag
  289. jQuery('div#issue_actions_container:first').prepend('<div id="review_button_div" style="width: 100%;margin-bottom:10px;"><button id="review_button">Code Review</button></div>');
  290. var view = createReviewView();
  291. var revView = false;
  292. jQuery('#review_button').click(function() {
  293. var oldView = jQuery('table', 'div#issue_actions_container').detach();
  294. jQuery('div#review_button_div').after(view);
  295. jQuery('#review_button').text(revView ? 'Code Review' : 'All Commits');
  296. view = oldView;
  297. revView = !revView;
  298. });
  299. }
  300. } else {
  301. if (jQuery('li#subversion-commits-tabpanel').hasClass('active') == 1) { //where the Subversion Commits is but not inside a tag
  302. jQuery('ul#issue-tabs').closest('div').after('<div id="review_button_div" style="width: 100%;margin-bottom:10px;"><button id="review_button">Code Review</button></div>');
  303. var view = createReviewView();
  304. var revView = false;
  305. jQuery('#review_button').click(function() {
  306. var oldView = jQuery('div#issue_actions_container').detach();
  307. jQuery('div#review_button_div').after(view);
  308. jQuery('#review_button').text(revView ? 'Code Review' : 'All Commits');
  309. view = oldView;
  310. revView = !revView;
  311. });
  312. }
  313. }
  314. }
  315. }
  316.  
  317. function allIssuesDefault(version) {
  318. if (jQuery('*[data-item-id="allissues"]').length) {
  319. jQuery('*[data-item-id="allissues"]').trigger('click');
  320. }
  321. }
  322.  
  323. function colorTasks(issueTable, version) {
  324. if (issueTable.length && (issueTable.find('td.timeoriginalestimate').length || issueTable.find('td.aggregatetimeoriginalestimate').length) && issueTable.find('td.duedate').length) {
  325. var $issueTable = issueTable,
  326. $issueTableRows = issueTable.find('tr:gt(0)'); // skip the header row
  327.  
  328. $issueTableRows.each(function(index) {
  329. var $originalEstimate = (jQuery.trim(jQuery('td.timeoriginalestimate', this).html()) == '' ? jQuery.trim(jQuery('td.aggregatetimeoriginalestimate', this).html()) : jQuery.trim(jQuery('td.timeoriginalestimate', this).html())),
  330. $dueDate = (typeof(jQuery('td.duedate', this).html()) == 'string' ? jQuery('td.duedate', this).html() : ''),
  331. $originalEstimateDays,
  332. $originalEstimateMinutes,
  333. $numberDays,
  334. thisdate,
  335. today;
  336.  
  337. // Get total estimated minutes
  338. $originalEstimateMinutes = ($originalEstimate.match(/\d+(?= week)/ig) == null ? 0 : parseInt($originalEstimate.match(/\d+(?= week)/ig)) * 2400)
  339. + ($originalEstimate.match(/\d+(?= day)/ig) == null ? 0 : parseInt($originalEstimate.match(/\d+(?= day)/ig)) * 480)
  340. + ($originalEstimate.match(/\d+(?= hour)/ig) == null ? 0 : parseInt($originalEstimate.match(/\d+(?= hour)/ig)) * 60)
  341. + ($originalEstimate.match(/\d+(?= minute)/ig) == null ? 0 : parseInt($originalEstimate.match(/\d+(?= minute)/ig)));
  342.  
  343. // Now let's account for weekends
  344. $numberDays = Math.ceil($originalEstimateMinutes / 480);
  345.  
  346. thisdate = new Date();
  347. today = thisdate.getDay();
  348.  
  349. switch (true) {
  350. case ($numberDays == 0):
  351. if (today == 6) {
  352. $originalEstimateMinutes += 480;
  353. } else if (today == 5) {
  354. $originalEstimateMinutes += 960;
  355. }
  356. break;
  357. case ($numberDays == 1):
  358. if (today == 6) {
  359. $originalEstimateMinutes += 480;
  360. } else if (today == 5) {
  361. $originalEstimateMinutes += 960;
  362. }
  363. break;
  364. case ($numberDays == 2):
  365. if (today == 6) {
  366. $originalEstimateMinutes += 480;
  367. } else if (today >= 4) {
  368. $originalEstimateMinutes += 960;
  369. }
  370. break;
  371. case ($numberDays == 3):
  372. if (today == 6) {
  373. $originalEstimateMinutes += 480;
  374. } else if (today >= 3) {
  375. $originalEstimateMinutes += 960;
  376. }
  377. break;
  378. case ($numberDays == 4):
  379. if (today == 6) {
  380. $originalEstimateMinutes += 480;
  381. } else if (today >= 2) {
  382. $originalEstimateMinutes += 960;
  383. }
  384. break;
  385. case ($numberDays == 5):
  386. if (today == 6) {
  387. $originalEstimateMinutes += 480;
  388. } else if (today >= 1) {
  389. $originalEstimateMinutes += 960;
  390. }
  391. break;
  392. case ($numberDays >= 6 && $numberDays < 11):
  393. if (today == 6) {
  394. $originalEstimateMinutes += 1440;
  395. } else if (today >= 5) {
  396. $originalEstimateMinutes += 1920;
  397. } else if (today == 0) {
  398. $originalEstimateMinutes += 960;
  399. }
  400. break;
  401. case ($numberDays >= 11 && $numberDays < 16):
  402. if (today == 6) {
  403. $originalEstimateMinutes += 2400;
  404. } else if (today >= 5) {
  405. $originalEstimateMinutes += 2880;
  406. } else if (today == 0) {
  407. $originalEstimateMinutes += 1920;
  408. }
  409. break;
  410. case ($numberDays >= 16 && $numberDays < 21):
  411. if (today == 6) {
  412. $originalEstimateMinutes += 3360;
  413. } else if (today >= 5) {
  414. $originalEstimateMinutes += 3840;
  415. } else if (today == 0) {
  416. $originalEstimateMinutes += 2880;
  417. }
  418. break;
  419. case ($numberDays >= 21 && $numberDays < 26):
  420. if (today == 6) {
  421. $originalEstimateMinutes += 4350;
  422. } else if (today >= 5) {
  423. $originalEstimateMinutes += 4800;
  424. } else if (today == 0) {
  425. $originalEstimateMinutes += 3840;
  426. }
  427. break;
  428. default:
  429. break;
  430. }
  431.  
  432. // The number of total days estimated, including weekends
  433. $originalEstimateDays = Math.ceil($originalEstimateMinutes / 480);
  434. $daysTilDueDate = Math.ceil(dateDiff($dueDate));
  435.  
  436. if ($originalEstimate.trim() != '') { // If an Original Estimate was given
  437. if (($daysTilDueDate - $originalEstimateDays) < 1) {
  438. jQuery(this).css('background-color','#ff9999'); // Red
  439. } else if (($daysTilDueDate - $originalEstimateDays) <= 2) {
  440. jQuery(this).css('background-color','#fed080'); // Orange
  441. } else if (($daysTilDueDate - $originalEstimateDays) <= 4) {
  442. jQuery(this).css('background-color','#fffeab'); // Yellow
  443. }
  444. } else {
  445. if (($daysTilDueDate - $originalEstimateDays) < 1) {
  446. jQuery(this).css('background-color','#ff9999'); // Red
  447. } else if (($daysTilDueDate - $originalEstimateDays) == 1) {
  448. jQuery(this).css('background-color','#fed080'); // Orange
  449. } else if (($daysTilDueDate - $originalEstimateDays) == 2) {
  450. jQuery(this).css('background-color','#fffeab'); // Yellow
  451. }
  452. }
  453. });
  454. } else if (jQuery('.issue-list').length) {
  455. // Do nothing for now
  456. }
  457. }
  458.  
  459. function dateDiff(dueDate) {
  460. var dueDate = dueDate.replace('Jan', '0')
  461. .replace('Feb', '1')
  462. .replace('Mar', '2')
  463. .replace('Apr', '3')
  464. .replace('May', '4')
  465. .replace('Jun', '5')
  466. .replace('Jul', '6')
  467. .replace('Aug', '7')
  468. .replace('Sep', '8')
  469. .replace('Oct', '9')
  470. .replace('Nov', '10')
  471. .replace('Dec', '11');
  472. var arrDueDate = dueDate.split('/');
  473. var dt1 = new Date();
  474. var dt2 = new Date('20' + arrDueDate[2], arrDueDate[1], arrDueDate[0]);
  475. var one_day = 1000 * 60 * 60 * 24;
  476.  
  477. return (parseInt(dt2.getTime() - dt1.getTime()) / (one_day));
  478. }
  479. }
  480.  
  481. scriptLoader(mainCode);