ADO Build & Deploy - Run Pipeline+

Add useful options for Build & Deploy pipeline in Azure DevOps

Fra 19.01.2023. Se den seneste versjonen.

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