ECOD-Jira-List-Copier

Copy JIRA issues from sprints in one big list

目前为 2024-02-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ECOD-Jira-List-Copier
  3. // @namespace ecod.jira.list-copy
  4. // @version 1.1.2
  5. // @description Copy JIRA issues from sprints in one big list
  6. // @author CRK
  7. // @require https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-umd-min.js
  8. // @match https://jira.fsc.atos-services.net/secure/RapidBoard.jspa?rapidView*
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15. function hookTheMonkeyForJira() {
  16. var scriptFunctionCopyList = document.createElement('script');
  17. scriptFunctionCopyList.innerText = `function copyList(node, $case) {
  18. let tiles = node.parentNode.parentNode.parentNode.parentNode.nextElementSibling.querySelectorAll("div[data-issue-key]");
  19. let listData = [];
  20. tiles.forEach(function (tile) {
  21. if(tile.attributes && tile.attributes){
  22. let id = tile.attributes['data-issue-key'].value;
  23. let issue = '?';
  24. let type = '?';
  25. let status = '?';
  26. let priority = '?';
  27. let link = '?';
  28.  
  29. if(tile.childNodes[0] && tile.childNodes[0].attributes && tile.childNodes[0].attributes['aria-label']){
  30. issue = tile.childNodes[0].attributes['aria-label'].value;
  31. }
  32.  
  33. if(tile.childNodes[1] && tile.childNodes[1].childNodes[0] && tile.childNodes[1].childNodes[0].childNodes[0] && tile.childNodes[1].childNodes[0].childNodes[0].attributes['title']){
  34. type = tile.childNodes[1].childNodes[0].childNodes[0].attributes['title'].value;
  35. }
  36.  
  37. if(tile.childNodes[1] && tile.childNodes[1].childNodes[0] && tile.childNodes[1].childNodes[0].childNodes[1] && tile.childNodes[1].childNodes[0].childNodes[1].childNodes[0].attributes['title']){
  38. priority = tile.childNodes[1].childNodes[0].childNodes[1].childNodes[0].attributes['title'].value;
  39. }
  40.  
  41. if(tile.childNodes[1] && tile.childNodes[1].childNodes[0] && tile.childNodes[1].childNodes[0].childNodes[2] && tile.childNodes[1].childNodes[0].childNodes[2].childNodes[0].attributes['href']){
  42. link = 'https://jira.fsc.atos-services.net' + tile.childNodes[1].childNodes[0].childNodes[2].childNodes[0].attributes['href'].value;
  43. }
  44.  
  45. if(tile.childNodes[1] && tile.childNodes[1].childNodes[1] && tile.childNodes[1].childNodes[1].childNodes[0] && tile.childNodes[1].childNodes[1].childNodes[0].childNodes[0]){
  46. status = tile.childNodes[1].childNodes[1].childNodes[0].childNodes[0].innerText;
  47. }
  48.  
  49. listData.push({'id': id, 'issue': issue, 'type': type, 'status': status, 'priority': priority, 'link': link});
  50. }
  51. });
  52.  
  53. listData.sort((a, b) => {
  54. if (a.type < b.type) return -1;
  55. if (a.type > b.type) return 1;
  56. if (a.priority < b.priority) return -1;
  57. if (a.priority > b.priority) return 1;
  58. return 0;
  59. });
  60.  
  61. clip = '';
  62.  
  63. switch($case){
  64. case 'simple':
  65. listData.forEach((row) => clip += ' - ' + row.issue + '\\r\\n');
  66. break;
  67. case 'lines':
  68. listData.forEach((row) => clip += '[' + row.type + '] [' + row.priority + '] ' + row.issue + '\\r\\n');
  69. break;
  70. case 'csv':
  71. listData.forEach((row) => clip += row.type + ';' + row.priority + ';' + row.issue + ';' + row.status + ';' + row.link + '\\r\\n');
  72. break;
  73. default:
  74. listData.forEach((row) => clip += '[' + row.type + '][' + row.priority + ']' + row.issue + '\\r\\n');
  75.  
  76. };
  77.  
  78. navigator.clipboard.writeText(_.unescape(clip));
  79. node.className='monkeyButtons elementToFadeInAndOut';
  80. setTimeout(
  81. function(){
  82. node.className='monkeyButtons';
  83. }, 500
  84. );
  85. }`;
  86.  
  87. var style = document.createElement('style');
  88. style.innerText = `
  89. .elementToFadeInAndOut {
  90. animation: fadeInOut 0.5s linear forwards;
  91. }
  92. @keyframes fadeInOut {
  93. 0% { opacity:0; }
  94. 50% { opacity:0.5; }
  95. 100% { opacity:1; }
  96. }
  97. .monkeyButtons {
  98. cursor:pointer;
  99. background-color: #371cf2;;
  100. width: auto;
  101. border-radius: 5px;
  102. color: white;
  103. font-size: 12px;
  104. font-weight: bold;
  105. padding-top: 2px;
  106. padding-bottom: 2px;
  107. }`;
  108.  
  109. document.body.appendChild(scriptFunctionCopyList);
  110. document.body.appendChild(style);
  111. }
  112.  
  113. function addButtonToNode(node) {
  114. var copyButton0 = document.createElement('span');
  115. copyButton0.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; padding-right: 10px;");
  116. copyButton0.classList.add('ghx-sprint-info');
  117. copyButton0.innerHTML = `<span
  118. class='monkeyButtons'
  119. name='copyList'
  120. id='copyList'
  121. onclick='copyList(this, "simple");'
  122. title='Copy list as simple list'>Copy as Simple-list</span>`;
  123.  
  124. var copyButton1 = document.createElement('span');
  125. copyButton1.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; padding-right: 10px;");
  126. copyButton1.classList.add('ghx-sprint-info');
  127. copyButton1.innerHTML = `<span
  128. class='monkeyButtons'
  129. name='copyList'
  130. id='copyList'
  131. onclick='copyList(this, "lines");'
  132. title='Copy list as Detailed List'>Copy as Detailed-list</span>`;
  133.  
  134. var copyButton2 = document.createElement('span');
  135. copyButton2.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; margin-right:40px;");
  136. copyButton2.classList.add('ghx-sprint-info');
  137. copyButton2.innerHTML = `<span
  138. class='monkeyButtons'
  139. name='copyList'
  140. id='copyList'
  141. onclick='copyList(this, "csv");'
  142. title='Copy list as CSV'>Copy as CSV-export</span>`;
  143.  
  144.  
  145. if(node && node.firstChild && node.firstChild.getElementsByClassName('ghx-sprint-info')) {
  146. let divContainer = document.createElement('div');
  147. divContainer.appendChild(copyButton0);
  148. divContainer.appendChild(copyButton1);
  149. divContainer.appendChild(copyButton2);
  150. divContainer.setAttribute("style", "margin-left: auto;");
  151. if(node.firstChild.getElementsByClassName('ghx-sprint-info')[0]){
  152. node.firstChild.getElementsByClassName('ghx-sprint-info')[0].appendChild(divContainer);
  153. }
  154. }
  155. }
  156.  
  157. // Callback function to observe mutations in the DOM
  158. function handleJiraMutations(mutationsList, observer) {
  159. for (var mutation of mutationsList) {
  160. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  161. mutation.addedNodes.forEach(function(node) {
  162. //console.debug("NODE: ", node.attributes && node.attributes['data-sprint-id']);
  163. if (node.attributes && node.attributes['data-sprint-id']) {
  164. //console.debug("FOUND: " + node.id);
  165. addButtonToNode(node);
  166. }
  167. });
  168. }
  169. }
  170. }
  171.  
  172. hookTheMonkeyForJira();
  173. // Create a new MutationObserver
  174. var boardObserver = new MutationObserver(handleJiraMutations);
  175.  
  176. // Start observing the body for DOM changes
  177. boardObserver.observe(document.body, {
  178. childList: true,
  179. subtree: true
  180. });
  181.  
  182. })();