ADO Build & Deploy - Run Pipeline+

Add useful options for Build & Deploy pipeline in Azure DevOps

От 12.01.2023. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         ADO Build & Deploy - Run Pipeline+
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @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*
// @icon         https://www.google.com/s2/favicons?domain=azure.com
// @grant        none
// @license      MIT
// ==/UserScript==

(async function() {
    "use strict";

    // Constantes
    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 = "arpege-web-build-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 result = document.querySelectorAll(_selector);
        if (result.length > 0) {
            return;
        } else if (maxRetry > 0) {
            console.debug(`[waitFor] [${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 = document.querySelectorAll(PARAMETERS_SELECTOR);
        const svcElements = [];
        let databasePrepareEltFound = false;
        // Ignore all parameters until "databasePrepare" parameter
        elements.forEach((_elt, _idx) => {
            if (databasePrepareEltFound) {
                svcElements.push(_elt);
            } else if (_elt.innerHTML.includes("databasePrepare")) {
                databasePrepareEltFound = true;
            }
        });
        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() {
        console.debug("ADO Arpege-Web Build & Compile - Run Pipeline Plus");

        // Search by ID Run Pipeline button
        const buttonRunPipeline = document.getElementById(RUN_PIPELINE_SELECTOR);
        if (typeof buttonRunPipeline === "undefined") {
            console.warn("Cannot find Run pipeline button");
            return;
        } else {
            console.debug("[run] Found Run pipeline button");
        }

        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);
            } catch (_err) {
                console.error(_err);
            }
        });
    }

    run();
})();