ADO Build & Deploy - Run Pipeline+

Add useful options for Build & Deploy pipeline in Azure DevOps

As of 2023-01-12. See the latest version.

  1. // ==UserScript==
  2. // @name ADO Build & Deploy - Run Pipeline+
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.0
  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. // @icon https://www.google.com/s2/favicons?domain=azure.com
  14. // @grant none
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (async function() {
  19. "use strict";
  20.  
  21. // Constantes
  22. const TEXT = {
  23. "fr": {
  24. buttonAllServices: "Tous les services"
  25. },
  26. "en": {
  27. buttonAllServices: "All services"
  28. }
  29. };
  30. const LANGUAGE = getLanguage();
  31. const RUN_PIPELINE_SELECTOR = "__bolt-run-pipeline-command";
  32. const POPUP_SELECTOR = ".bolt-panel-callout-content";
  33. const PARAMETERS_SELECTOR = ".padding-horizontal-20.rhythm-vertical-16 > .flex-noshrink";
  34. const BRANCHES_DROPDOWN_INPUT_SELECTOR = ".version-dropdown > input";
  35. const BUTTON_ID = "arpege-web-build-deploy-all-service-button";
  36.  
  37. /**
  38. * Returns navigator's language.
  39. * @returns {string} Language. Default "en".
  40. */
  41. function getLanguage() {
  42. // Get language from navigator variable
  43. let language = (
  44. typeof navigator === "object" &&
  45. navigator.language &&
  46. navigator.language.split("-").shift()
  47. );
  48.  
  49. // If not text found, set "en" as default
  50. if (typeof TEXT[language] === "undefined") {
  51. language = "en";
  52. }
  53.  
  54. return language;
  55. }
  56.  
  57. /**
  58. * Wait for the appearance of the HTML elements with the selector.
  59. * @param {string} _selector - The selector
  60. * @param {object} options - Options
  61. * @param {string} options.name - Selector name (Default to _selector value)
  62. * @param {number} options.maxRetry - Number of retry (Default to 0)
  63. * @param {number} options.timeout - Time to wait before retrying (Default to 1 sec)
  64. * @returns {Promise<void>} Empty promise.
  65. * @async
  66. */
  67. async function waitFor(_selector, {name = _selector, maxRetry = 0, timeout = 1000} = {}) {
  68. const result = document.querySelectorAll(_selector);
  69. if (result.length > 0) {
  70. return;
  71. } else if (maxRetry > 0) {
  72. console.debug(`[waitFor] [${name}] Remaining retries: ${--maxRetry}`);
  73.  
  74. await new Promise((_resolve, _reject) => {
  75. setTimeout(async () => {
  76. try {
  77. await waitFor(_selector, {name, maxRetry, timeout});
  78. _resolve();
  79. } catch (_err) {
  80. _reject(_err);
  81. }
  82. }, timeout);
  83. });
  84. } else {
  85. throw new Error(`Cannot find elements with selector: ${_selector}`);
  86. }
  87. }
  88.  
  89. /**
  90. * Get services HTML elements from Run Pipeline popup.
  91. * @returns {void} Nothing.
  92. */
  93. function getServiceElements() {
  94. const popup = document.querySelector(POPUP_SELECTOR);
  95. const elements = document.querySelectorAll(PARAMETERS_SELECTOR);
  96. const svcElements = [];
  97. let databasePrepareEltFound = false;
  98. // Ignore all parameters until "databasePrepare" parameter
  99. elements.forEach((_elt, _idx) => {
  100. if (databasePrepareEltFound) {
  101. svcElements.push(_elt);
  102. } else if (_elt.innerHTML.includes("databasePrepare")) {
  103. databasePrepareEltFound = true;
  104. }
  105. });
  106. return svcElements;
  107. }
  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.  
  125. // Add click event listener
  126. buttonAllServices.addEventListener("click", (_event) => {
  127. _event.stopPropagation();
  128. _event.preventDefault();
  129.  
  130. const oldChecked = buttonAllServices.getAttribute("aria-checked") === "true";
  131. const newChecked = !oldChecked;
  132. const action = newChecked === true ? "add" : "remove";
  133.  
  134. // Update aria-checked
  135. buttonAllServices.setAttribute("aria-checked", newChecked);
  136. buttonAllServices.classList[action]("checked");
  137.  
  138. // Get services' parameters elements and all parameters elements
  139. const svcElements = getServiceElements();
  140. for (let svcElt of svcElements) {
  141. const svcChecked = svcElt.getAttribute("aria-checked") === "true";
  142. // Button is checked but service is not, or button is unchecked and service is.
  143. if (newChecked && !svcChecked || !newChecked && svcChecked) {
  144. svcElt.click();
  145. }
  146. }
  147.  
  148. buttonAllServices.focus();
  149. });
  150.  
  151. // Add div element before first service element
  152. firstSvcElement.insertAdjacentElement("beforebegin", buttonAllServices);
  153.  
  154. return buttonAllServices;
  155. }
  156.  
  157. async function run() {
  158. console.debug("ADO Arpege-Web Build & Compile - Run Pipeline Plus");
  159.  
  160. // Search by ID Run Pipeline button
  161. const buttonRunPipeline = document.getElementById(RUN_PIPELINE_SELECTOR);
  162. if (typeof buttonRunPipeline === "undefined") {
  163. console.warn("Cannot find Run pipeline button");
  164. return;
  165. } else {
  166. console.debug("[run] Found Run pipeline button");
  167. }
  168.  
  169. buttonRunPipeline.addEventListener("click", async () => {
  170. try {
  171. // Wait 5 sec max for parameters elements to be present in Run Pipeline popup
  172. await waitFor(PARAMETERS_SELECTOR, {name: "Services parameters", maxRetry: 10, timeout: 500});
  173.  
  174. // Get services' parameters elements and all parameters elements
  175. const svcElements = getServiceElements();
  176.  
  177. // Add div element before first service element
  178. const buttonAllServices = createButtonAllServices(svcElements);
  179. } catch (_err) {
  180. console.error(_err);
  181. }
  182. });
  183. }
  184.  
  185. run();
  186. })();