Add useful options for Build & Deploy pipeline in Azure DevOps
- // ==UserScript==
- // @name ADO Build & Deploy - Run Pipeline+
- // @namespace http://tampermonkey.net/
- // @version 1.0.2
- // @description Add useful options for Build & Deploy pipeline in Azure DevOps
- // @author Victor Ros
- // @match https://*.visualstudio.com/*/_build?definitionId=3237*
- // @match https://dev.azure.com/*/*/_build?definitionId=3237*
- // @match https://*.visualstudio.com/*/_build?definitionId=3333*
- // @match https://dev.azure.com/*/*/_build?definitionId=3333*
- // @match https://*.visualstudio.com/*/_build?definitionId=4569*
- // @match https://dev.azure.com/*/*/_build?definitionId=4569*
- // @match https://*.visualstudio.com/*/_build?definitionId=5712*
- // @match https://dev.azure.com/*/*/_build?definitionId=5712*
- // @icon https://www.google.com/s2/favicons?domain=azure.com
- // @grant none
- // @license MIT
- // ==/UserScript==
- (async function() {
- "use strict";
- const log = console.log;
- // Constants
- const PREFIX_LOG = "[ADO Build & Deploy - Run Pipeline+]";
- const TEXT = {
- "fr": {
- buttonAllServices: "Tous les services"
- },
- "en": {
- buttonAllServices: "All services"
- }
- };
- const LANGUAGE = getLanguage();
- const RUN_PIPELINE_SELECTOR = "__bolt-run-pipeline-command";
- const POPUP_SELECTOR = ".bolt-panel-callout-content";
- const PARAMETERS_SELECTOR = ".padding-horizontal-20.rhythm-vertical-16 > .flex-noshrink";
- const BRANCHES_DROPDOWN_INPUT_SELECTOR = ".version-dropdown > input";
- const BUTTON_ID = "deploy-all-service-button";
- /**
- * Returns navigator's language.
- * @returns {string} Language. Default "en".
- */
- function getLanguage() {
- // Get language from navigator variable
- let language = (
- typeof navigator === "object" &&
- navigator.language &&
- navigator.language.split("-").shift()
- );
- // If not text found, set "en" as default
- if (typeof TEXT[language] === "undefined") {
- language = "en";
- }
- return language;
- }
- /**
- * Wait for the appearance of the HTML elements with the selector.
- * @param {string} _selector - The selector
- * @param {object} options - Options
- * @param {string} options.name - Selector name (Default to _selector value)
- * @param {number} options.maxRetry - Number of retry (Default to 0)
- * @param {number} options.timeout - Time to wait before retrying (Default to 1 sec)
- * @returns {Promise<void>} Empty promise.
- * @async
- */
- async function waitFor(_selector, {name = _selector, maxRetry = 0, timeout = 1000} = {}) {
- const r###lt = document.querySelectorAll(_selector);
- if (r###lt.length > 0) {
- log(`${PREFIX_LOG} ${name} found (${r###lt.length})`);
- return;
- } else if (maxRetry > 0) {
- log(`${PREFIX_LOG} Wait for ${name} (Remaining retries: ${--maxRetry})`);
- await new Promise((_resolve, _reject) => {
- setTimeout(async () => {
- try {
- await waitFor(_selector, {name, maxRetry, timeout});
- _resolve();
- } catch (_err) {
- _reject(_err);
- }
- }, timeout);
- });
- } else {
- throw new Error(`Cannot find elements with selector: ${_selector}`);
- }
- }
- /**
- * Get services HTML elements from Run Pipeline popup.
- * @returns {void} Nothing.
- */
- function getServiceElements() {
- const popup = document.querySelector(POPUP_SELECTOR);
- const elements = popup.querySelectorAll(PARAMETERS_SELECTOR);
- const svcElements = [];
- let firstServiceFound = false;
- // Ignore all parameters until "events-service" parameter
- elements.forEach((_elt, _idx) => {
- if (_elt.innerHTML.includes("events-service")) {
- firstServiceFound = true;
- }
- if (firstServiceFound) {
- svcElements.push(_elt);
- }
- });
- if (svcElements.length === 0) {
- log(`${PREFIX_LOG} Something went wrong while retrieving service elements.`);
- }
- return svcElements;
- }
- /**
- * Add a button to select/unselect all services.
- * Recreate the HTML button.
- * @returns {HTMLElement} Button "All services".
- */
- function createButtonAllServices(_svcElements) {
- const firstSvcElement = _svcElements[0];
- const buttonAllServices = firstSvcElement.cloneNode(true);
- // Unselect by default
- buttonAllServices.setAttribute("aria-checked", false);
- buttonAllServices.classList.remove("checked");
- // Override id from div child
- const divChild = buttonAllServices.querySelector(".bolt-checkbox-label");
- divChild.setAttribute("id", BUTTON_ID);
- divChild.innerHTML = TEXT[LANGUAGE].buttonAllServices;
- // Add click event listener
- buttonAllServices.addEventListener("click", (_event) => {
- _event.stopPropagation();
- _event.preventDefault();
- const oldChecked = buttonAllServices.getAttribute("aria-checked") === "true";
- const newChecked = !oldChecked;
- const action = newChecked === true ? "add" : "remove";
- // Update aria-checked
- buttonAllServices.setAttribute("aria-checked", newChecked);
- buttonAllServices.classList[action]("checked");
- // Get services' parameters elements and all parameters elements
- const svcElements = getServiceElements();
- for (let svcElt of svcElements) {
- const svcChecked = svcElt.getAttribute("aria-checked") === "true";
- // Button is checked but service is not, or button is unchecked and service is.
- if (newChecked && !svcChecked || !newChecked && svcChecked) {
- svcElt.click();
- }
- }
- buttonAllServices.focus();
- });
- // Add div element before first service element
- firstSvcElement.insertAdjacentElement("beforebegin", buttonAllServices);
- return buttonAllServices;
- }
- async function run() {
- log(`${PREFIX_LOG} Init...`);
- // Search by ID Run Pipeline button
- const buttonRunPipeline = document.getElementById(RUN_PIPELINE_SELECTOR);
- if (typeof buttonRunPipeline === "undefined") {
- log(`${PREFIX_LOG} Cannot find Run pipeline button`);
- return;
- } else {
- log(`${PREFIX_LOG} Found Run pipeline button`, buttonRunPipeline);
- }
- buttonRunPipeline.addEventListener("click", async () => {
- try {
- // Wait 5 sec max for parameters elements to be present in Run Pipeline popup
- await waitFor(PARAMETERS_SELECTOR, {name: "Services parameters", maxRetry: 10, timeout: 500});
- // Get services' parameters elements and all parameters elements
- const svcElements = getServiceElements();
- // Add div element before first service element
- const buttonAllServices = createButtonAllServices(svcElements);
- log(`${PREFIX_LOG} Added button "${TEXT[LANGUAGE].buttonAllServices}"`);
- } catch (_err) {
- log(`${PREFIX_LOG} ${_err.stack}`);
- }
- });
- log(`${PREFIX_LOG} Finished!`);
- }
- // Events
- window.addEventListener("load", run);
- window.removeEventListener("unload", run);
- })();