ECOD-Jira-List-Copier

Copy JIRA issues from sprints in one big list

  1. // ==UserScript==
  2. // @name ECOD-Jira-List-Copier
  3. // @namespace ecod.jira.list-copy
  4. // @version 1.1.3
  5. // @description Copy JIRA issues from sprints in one big list
  6. // @author CRK
  7. // @require https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
  8. // @match https://jira.fsc.atos-services.net/secure/RapidBoard.jspa?rapidView=2102&projectKey=ECOD&view=planning.nodetail*
  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='monkeyCtlButtons elementToFadeInAndOut';
  80. setTimeout(
  81. function(){
  82. node.className='monkeyCtlButtons';
  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. .monkeyCtlButtons {
  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-left: 5px;
  107. padding-right: 5px;
  108. padding-bottom: 2px;
  109. display: inline-block;
  110. }`;
  111.  
  112. document.body.appendChild(scriptFunctionCopyList);
  113. document.body.appendChild(style);
  114. }
  115.  
  116. function addButtonToNode(node) {
  117. var copyButton0 = document.createElement('span');
  118. copyButton0.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; padding-right: 10px;");
  119. copyButton0.classList.add('ghx-sprint-info');
  120. copyButton0.innerHTML = `<span
  121. class='monkeyCtlButtons'
  122. name='copyList'
  123. id='copyList'
  124. onclick='copyList(this, "simple");'
  125. title='Copy list as simple list'>Copy as Simple-list</span>`;
  126.  
  127. var copyButton1 = document.createElement('span');
  128. copyButton1.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; padding-right: 10px;");
  129. copyButton1.classList.add('ghx-sprint-info');
  130. copyButton1.innerHTML = `<span
  131. class='monkeyCtlButtons'
  132. name='copyList'
  133. id='copyList'
  134. onclick='copyList(this, "lines");'
  135. title='Copy list as Detailed List'>Copy as Detailed-list</span>`;
  136.  
  137. var copyButton2 = document.createElement('span');
  138. copyButton2.setAttribute("style" ,"cursor:pointer; display: inline; padding-left: 0px; margin-right:40px;");
  139. copyButton2.classList.add('ghx-sprint-info');
  140. copyButton2.innerHTML = `<span
  141. class='monkeyCtlButtons'
  142. name='copyList'
  143. id='copyList'
  144. onclick='copyList(this, "csv");'
  145. title='Copy list as CSV'>Copy as CSV-export</span>`;
  146.  
  147.  
  148. if(node && node.firstChild && node.firstChild.getElementsByClassName('ghx-sprint-info')) {
  149. let divContainer = document.createElement('div');
  150. divContainer.appendChild(copyButton0);
  151. divContainer.appendChild(copyButton1);
  152. divContainer.appendChild(copyButton2);
  153. divContainer.setAttribute("style", "margin-left: auto;");
  154. if(node.firstChild.getElementsByClassName('ghx-sprint-info')[0]){
  155. node.firstChild.getElementsByClassName('ghx-sprint-info')[0].appendChild(divContainer);
  156. }
  157. }
  158. }
  159.  
  160. // Callback function to observe mutations in the DOM
  161. function handleJiraMutations(mutationsList, observer) {
  162. for (var mutation of mutationsList) {
  163. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  164. mutation.addedNodes.forEach(function(node) {
  165. //console.debug("NODE: ", node.attributes && node.attributes['data-sprint-id']);
  166. if (node.attributes && node.attributes['data-sprint-id']) {
  167. //console.debug("FOUND: " + node.id);
  168. addButtonToNode(node);
  169. }
  170. });
  171. }
  172. }
  173. }
  174.  
  175. hookTheMonkeyForJira();
  176. // Create a new MutationObserver
  177. var boardObserver = new MutationObserver(handleJiraMutations);
  178.  
  179. // Start observing the body for DOM changes
  180. boardObserver.observe(document.body, {
  181. childList: true,
  182. subtree: true
  183. });
  184.  
  185. })();