Open Launchpad Kernel SRU Verification Bugs

Adds a button to open verification bugs in new windows on Launchpad bug pages, excluding the current bug ID

  1. // ==UserScript==
  2. // @name Open Launchpad Kernel SRU Verification Bugs
  3. // @namespace http://anthonywong.net/
  4. // @version 1.2
  5. // @description Adds a button to open verification bugs in new windows on Launchpad bug pages, excluding the current bug ID
  6. // @author Grok
  7. // @match https://bugs.launchpad.net/kernel-sru-workflow/+bug/*
  8. // @grant GM_openInTab
  9. // @license GPLv2
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. console.log('Tampermonkey script started');
  16.  
  17. // Extract current bug ID from URL
  18. const url = window.location.href;
  19. const urlRegex = /\+bug\/(\d+)/;
  20. const urlMatch = url.match(urlRegex);
  21. const currentBugId = urlMatch ? urlMatch[1] : null;
  22. console.log('Current bug ID from URL:', currentBugId);
  23.  
  24. // Find the div with class yui3-editable_text-text
  25. const div = document.querySelector('div.yui3-editable_text-text');
  26. if (!div) {
  27. console.log('No div with class yui3-editable_text-text found');
  28. return;
  29. }
  30. console.log('Found div with class yui3-editable_text-text');
  31.  
  32. // Find the p element inside the div
  33. const pElements = div.getElementsByTagName('p');
  34. console.log(`Found ${pElements.length} p elements in div`);
  35.  
  36. let targetP;
  37. let bugNumbers = [];
  38.  
  39. // Iterate through p elements to find the one with verification-bugs
  40. for (let p of pElements) {
  41. if (p.textContent.includes('verification-bugs')) {
  42. console.log('Found verification-bugs in p element');
  43. console.log('P element content (first 200 chars):', p.textContent.substring(0, 200));
  44. targetP = p;
  45. // Extract bug numbers using regex
  46. const bugsRegex = /verification-bugs:\s*\[([\d,\s]*)\]/;
  47. const match = p.textContent.match(bugsRegex);
  48. if (match && match[1]) {
  49. bugNumbers = match[1].split(',').map(num => num.trim()).filter(num => num);
  50. console.log('Extracted bug numbers:', bugNumbers);
  51. } else {
  52. console.log('No bug numbers matched in regex');
  53. }
  54. break;
  55. }
  56. }
  57.  
  58. if (!targetP) {
  59. console.log('No p element with verification-bugs found in div');
  60. return;
  61. }
  62.  
  63. if (bugNumbers.length === 0) {
  64. console.log('No bug numbers extracted');
  65. return;
  66. }
  67.  
  68. // Check if button already exists
  69. const existingButtons = div.querySelectorAll('button');
  70. for (let btn of existingButtons) {
  71. if (btn.textContent === 'Open bugs in new window') {
  72. console.log('Button already exists, skipping');
  73. return;
  74. }
  75. }
  76.  
  77. // Create button
  78. console.log('Creating button');
  79. const button = document.createElement('button');
  80. button.textContent = 'Open bugs in new window';
  81. button.style.marginLeft = '10px';
  82. button.style.cursor = 'pointer';
  83. button.style.padding = '5px 10px';
  84. button.style.backgroundColor = '#007bff';
  85. button.style.color = 'white';
  86. button.style.border = 'none';
  87. button.style.borderRadius = '4px';
  88. button.style.display = 'inline-block';
  89. button.style.verticalAlign = 'middle';
  90.  
  91. // Add click event listener, excluding current bug ID
  92. button.addEventListener('click', () => {
  93. console.log('Button clicked, processing bug numbers:', bugNumbers);
  94. const filteredBugNumbers = bugNumbers.filter(num => num !== currentBugId);
  95. console.log('Filtered bug numbers (excluding current bug ID):', filteredBugNumbers);
  96. filteredBugNumbers.forEach(bugNumber => {
  97. const url = `http://launchpad.net/bugs/${bugNumber}`;
  98. console.log(`Opening tab for bug ${bugNumber}: ${url}`);
  99. GM_openInTab(url, { active: false });
  100. });
  101. });
  102.  
  103. // Insert button inline after verification-bugs line
  104. console.log('Modifying p element to insert button');
  105. const innerHTML = targetP.innerHTML;
  106. // Log a snippet of innerHTML around verification-bugs for debugging
  107. const verificationIndex = innerHTML.toLowerCase().indexOf('verification-<wbr>bugs');
  108. if (verificationIndex !== -1) {
  109. const snippetStart = Math.max(0, verificationIndex - 100);
  110. const snippetEnd = verificationIndex + 200;
  111. console.log('innerHTML snippet around verification-bugs:', innerHTML.substring(snippetStart, snippetEnd));
  112. } else {
  113. console.log('verification-bugs not found in innerHTML');
  114. }
  115.  
  116. // Find the verification-bugs text node (search for 'verification-' due to <wbr>)
  117. const textNodes = document.evaluate(
  118. ".//text()[contains(., 'verification-')]",
  119. targetP,
  120. null,
  121. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  122. null
  123. );
  124.  
  125. if (textNodes.snapshotLength > 0) {
  126. console.log(`Found ${textNodes.snapshotLength} text nodes containing 'verification-'`);
  127. const verificationNode = textNodes.snapshotItem(0);
  128. let currentNode = verificationNode;
  129. let foundBracket = false;
  130.  
  131. // Traverse until we find the closing ]
  132. while (currentNode && !foundBracket) {
  133. if (currentNode.nodeType === Node.TEXT_NODE && currentNode.textContent.includes(']')) {
  134. foundBracket = true;
  135. // Split the text node at ]
  136. const textContent = currentNode.textContent;
  137. const bracketIndex = textContent.indexOf(']');
  138. if (bracketIndex !== -1) {
  139. const beforeBracket = textContent.substring(0, bracketIndex + 1);
  140. const afterBracket = textContent.substring(bracketIndex + 1);
  141. currentNode.textContent = beforeBracket;
  142. if (afterBracket) {
  143. const newTextNode = document.createTextNode(afterBracket);
  144. currentNode.parentNode.insertBefore(newTextNode, currentNode.nextSibling);
  145. }
  146. // Insert button after the ]
  147. console.log('Inserting button after closing ] of bug list');
  148. currentNode.parentNode.insertBefore(button, currentNode.nextSibling);
  149. }
  150. }
  151. currentNode = currentNode.nextSibling;
  152. }
  153.  
  154. if (!foundBracket) {
  155. console.log('No closing ] found, appending to p');
  156. targetP.appendChild(button);
  157. }
  158. } else {
  159. console.log('No verification- text node found, appending button after p');
  160. targetP.insertAdjacentElement('afterend', button);
  161. }
  162.  
  163. console.log('Button insertion complete');
  164. })();