🏠 Home 

ADO Build & Deploy - Run Pipeline+

Add useful options for Build & Deploy pipeline in Azure DevOps


Install this script?
  1. // ==UserScript==
  2. // @name ADO Build & Deploy - Run Pipeline+
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.2
  5. // @description Add useful options for Build & Deploy pipeline in Azure DevOps
  6. // @author Victor Ros
  7. // @match https://*.visualstudio.com/*/_build?definitionId=3237*
  8. // @match https://dev.azure.com/*/*/_build?definitionId=3237*
  9. // @match https://*.visualstudio.com/*/_build?definitionId=3333*
  10. // @match https://dev.azure.com/*/*/_build?definitionId=3333*
  11. // @match https://*.visualstudio.com/*/_build?definitionId=4569*
  12. // @match https://dev.azure.com/*/*/_build?definitionId=4569*
  13. // @match https://*.visualstudio.com/*/_build?definitionId=5712*
  14. // @match https://dev.azure.com/*/*/_build?definitionId=5712*
  15. // @icon https://www.google.com/s2/favicons?domain=azure.com
  16. // @grant none
  17. // @license MIT
  18. // ==/UserScript==
  19. (async function() {
  20. "use strict";
  21. const log = console.log;
  22. // Constants
  23. const PREFIX_LOG = "[ADO Build & Deploy - Run Pipeline+]";
  24. const TEXT = {
  25. "fr": {
  26. buttonAllServices: "Tous les services"
  27. },
  28. "en": {
  29. buttonAllServices: "All services"
  30. }
  31. };
  32. const LANGUAGE = getLanguage();
  33. const RUN_PIPELINE_SELECTOR = "__bolt-run-pipeline-command";
  34. const POPUP_SELECTOR = ".bolt-panel-callout-content";
  35. const PARAMETERS_SELECTOR = ".padding-horizontal-20.rhythm-vertical-16 > .flex-noshrink";
  36. const BRANCHES_DROPDOWN_INPUT_SELECTOR = ".version-dropdown > input";
  37. const BUTTON_ID = "deploy-all-service-button";
  38. /**
  39. * Returns navigator's language.
  40. * @returns {string} Language. Default "en".
  41. */
  42. function getLanguage() {
  43. // Get language from navigator variable
  44. let language = (
  45. typeof navigator === "object" &&
  46. navigator.language &&
  47. navigator.language.split("-").shift()
  48. );
  49. // If not text found, set "en" as default
  50. if (typeof TEXT[language] === "undefined") {
  51. language = "en";
  52. }
  53. return language;
  54. }
  55. /**
  56. * Wait for the appearance of the HTML elements with the selector.
  57. * @param {string} _selector - The selector
  58. * @param {object} options - Options
  59. * @param {string} options.name - Selector name (Default to _selector value)
  60. * @param {number} options.maxRetry - Number of retry (Default to 0)
  61. * @param {number} options.timeout - Time to wait before retrying (Default to 1 sec)
  62. * @returns {Promise<void>} Empty promise.
  63. * @async
  64. */
  65. async function waitFor(_selector, {name = _selector, maxRetry = 0, timeout = 1000} = {}) {
  66. const r###lt = document.querySelectorAll(_selector);
  67. if (r###lt.length > 0) {
  68. log(`${PREFIX_LOG} ${name} found (${r###lt.length})`);
  69. return;
  70. } else if (maxRetry > 0) {
  71. log(`${PREFIX_LOG} Wait for ${name} (Remaining retries: ${--maxRetry})`);
  72. await new Promise((_resolve, _reject) => {
  73. setTimeout(async () => {
  74. try {
  75. await waitFor(_selector, {name, maxRetry, timeout});
  76. _resolve();
  77. } catch (_err) {
  78. _reject(_err);
  79. }
  80. }, timeout);
  81. });
  82. } else {
  83. throw new Error(`Cannot find elements with selector: ${_selector}`);
  84. }
  85. }
  86. /**
  87. * Get services HTML elements from Run Pipeline popup.
  88. * @returns {void} Nothing.
  89. */
  90. function getServiceElements() {
  91. const popup = document.querySelector(POPUP_SELECTOR);
  92. const elements = popup.querySelectorAll(PARAMETERS_SELECTOR);
  93. const svcElements = [];
  94. let firstServiceFound = false;
  95. // Ignore all parameters until "events-service" parameter
  96. elements.forEach((_elt, _idx) => {
  97. if (_elt.innerHTML.includes("events-service")) {
  98. firstServiceFound = true;
  99. }
  100. if (firstServiceFound) {
  101. svcElements.push(_elt);
  102. }
  103. });
  104. if (svcElements.length === 0) {
  105. log(`${PREFIX_LOG} Something went wrong while retrieving service elements.`);
  106. }
  107. return svcElements;
  108. }
  109. /**
  110. * Add a button to select/unselect all services.
  111. * Recreate the HTML button.
  112. * @returns {HTMLElement} Button "All services".
  113. */
  114. function createButtonAllServices(_svcElements) {
  115. const firstSvcElement = _svcElements[0];
  116. const buttonAllServices = firstSvcElement.cloneNode(true);
  117. // Unselect by default
  118. buttonAllServices.setAttribute("aria-checked", false);
  119. buttonAllServices.classList.remove("checked");
  120. // Override id from div child
  121. const divChild = buttonAllServices.querySelector(".bolt-checkbox-label");
  122. divChild.setAttribute("id", BUTTON_ID);
  123. divChild.innerHTML = TEXT[LANGUAGE].buttonAllServices;
  124. // Add click event listener
  125. buttonAllServices.addEventListener("click", (_event) => {
  126. _event.stopPropagation();
  127. _event.preventDefault();
  128. const oldChecked = buttonAllServices.getAttribute("aria-checked") === "true";
  129. const newChecked = !oldChecked;
  130. const action = newChecked === true ? "add" : "remove";
  131. // Update aria-checked
  132. buttonAllServices.setAttribute("aria-checked", newChecked);
  133. buttonAllServices.classList[action]("checked");
  134. // Get services' parameters elements and all parameters elements
  135. const svcElements = getServiceElements();
  136. for (let svcElt of svcElements) {
  137. const svcChecked = svcElt.getAttribute("aria-checked") === "true";
  138. // Button is checked but service is not, or button is unchecked and service is.
  139. if (newChecked && !svcChecked || !newChecked && svcChecked) {
  140. svcElt.click();
  141. }
  142. }
  143. buttonAllServices.focus();
  144. });
  145. // Add div element before first service element
  146. firstSvcElement.insertAdjacentElement("beforebegin", buttonAllServices);
  147. return buttonAllServices;
  148. }
  149. async function run() {
  150. log(`${PREFIX_LOG} Init...`);
  151. // Search by ID Run Pipeline button
  152. const buttonRunPipeline = document.getElementById(RUN_PIPELINE_SELECTOR);
  153. if (typeof buttonRunPipeline === "undefined") {
  154. log(`${PREFIX_LOG} Cannot find Run pipeline button`);
  155. return;
  156. } else {
  157. log(`${PREFIX_LOG} Found Run pipeline button`, buttonRunPipeline);
  158. }
  159. buttonRunPipeline.addEventListener("click", async () => {
  160. try {
  161. // Wait 5 sec max for parameters elements to be present in Run Pipeline popup
  162. await waitFor(PARAMETERS_SELECTOR, {name: "Services parameters", maxRetry: 10, timeout: 500});
  163. // Get services' parameters elements and all parameters elements
  164. const svcElements = getServiceElements();
  165. // Add div element before first service element
  166. const buttonAllServices = createButtonAllServices(svcElements);
  167. log(`${PREFIX_LOG} Added button "${TEXT[LANGUAGE].buttonAllServices}"`);
  168. } catch (_err) {
  169. log(`${PREFIX_LOG} ${_err.stack}`);
  170. }
  171. });
  172. log(`${PREFIX_LOG} Finished!`);
  173. }
  174. // Events
  175. window.addEventListener("load", run);
  176. window.removeEventListener("unload", run);
  177. })();