Add Text to Search Field in ADO and jump to lines with specific keywords. Untick pipeline stages.

Adds text to the search field on Azure DevOps pages

// ==UserScript==
// @name         Add Text to Search Field in ADO and jump to lines with specific keywords. Untick pipeline stages.
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds text to the search field on Azure DevOps pages
// @author       chaoscreater
// @match        https://dev.azure.com/*
// @grant        none
// ==/UserScript==


/*
(function() {
    'use strict';

    // Function to click on the search button and add text to the search field
    function addTextToSearchField() {
        const searchButton = document.querySelector('button#__bolt-log-search');
        if (searchButton) {
            searchButton.click(); // Click on the search button
            // Wait for a short delay to ensure the search field is fully populated
            setTimeout(() => {
                const searchField = document.querySelector('.find-box input.bolt-textfield-input');

                if (searchField) {
                  // set the input value using the native setter - see https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js
                  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                  // nativeInputValueSetter.call(searchField, 'Plan:');
                  nativeInputValueSetter.call(searchField, 'Terraform will perform the following actions:');
                  // searchField.value = 'Plan::';

                  // Dispatch an input event to simulate user input
                  searchField.dispatchEvent(new Event('input', { bubbles: true }));
                }
            }, 1000); // Adjust delay time as needed
        }
    }

    // Add text to the search field when the page is fully loaded
    window.addEventListener('load', addTextToSearchField);
})();
*/





// v1
/*
(function() {
    'use strict';

    // Function to click on the search button and add text to the search field
    function addTextToSearchField() {
        const searchButton = document.querySelector('button#__bolt-log-search');
        if (searchButton) {
            searchButton.click(); // Click on the search button
            // Wait for a short delay to ensure the search field is fully populated
            setTimeout(() => {
                const searchField = document.querySelector('.find-box input.bolt-textfield-input');

                if (searchField) {
                    // set the input value using the native setter - see https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js
                    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                    nativeInputValueSetter.call(searchField, 'Terraform will perform the following actions:');

                    // Dispatch an input event to simulate user input
                    searchField.dispatchEvent(new Event('input', { bubbles: true }));
                }
            }, 1000); // Adjust delay time as needed
        }
    }

    // Add text to the search field when the page is fully loaded
    window.addEventListener('load', addTextToSearchField);



    // Function to expand the search button
    function expandSearchButton() {
        const searchButton = document.querySelector('button[data-is-focusable="true"][aria-label="Search phrases"]');
        if (searchButton) {

          setTimeout(() => {
            searchButton.click(); // Click on the search button to expand it
          }, 1200);
        }
    }

    // Call the function to expand the search button when the page is fully loaded
    window.addEventListener('load', expandSearchButton);





    // Function to handle button click event
    function Terraform_action_start_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'Terraform will perform the following actions:');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));

            //searchField.value = 'Terraform will perform the following actions:'; // Add the desired text
            //searchField.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'})); // Simulate pressing Enter

        }
    }


    // Function to handle button click event
    function Terraform_plan_line_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'Plan:');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));

            //searchField.value = 'Terraform will perform the following actions:'; // Add the desired text
            //searchField.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'})); // Simulate pressing Enter

        }
    }


    // Function to handle button click event
    function Terraform_plan_no_changes_line_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'No changes.');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));

            //searchField.value = 'Terraform will perform the following actions:'; // Add the desired text
            //searchField.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'})); // Simulate pressing Enter

        }
    }


    // Create and append the button with CSS styling
    const button = document.createElement('button');
    button.textContent = 'Jump to Terraform action start';
    button.style.position = 'fixed';
    button.style.top = '80px'; // Adjusted top position
    button.style.left = '900px';
    button.style.zIndex = '9999';
    button.style.backgroundColor = 'green'; // Background color set to green
    button.addEventListener('click', Terraform_action_start_handleButtonClick);
    document.body.appendChild(button);

    // Create and append the button with CSS styling
    const button2 = document.createElement('button');
    button2.textContent = 'Jump to Plan line';
    button2.style.position = 'fixed';
    button2.style.top = '80px'; // Adjusted top position
    button2.style.left = '1120px';
    button2.style.zIndex = '9999';
    button2.style.backgroundColor = 'orange'; // Background color set to green
    button2.addEventListener('click', Terraform_plan_line_handleButtonClick);
    document.body.appendChild(button2);

    // Create and append the button with CSS styling
    const button3 = document.createElement('button');
    button3.textContent = 'Jump to No Changes';
    button3.style.position = 'fixed';
    button3.style.top = '80px'; // Adjusted top position
    button3.style.left = '1250px';
    button3.style.zIndex = '9999';
    button3.style.backgroundColor = 'orange'; // Background color set to green
    button3.addEventListener('click', Terraform_plan_no_changes_line_handleButtonClick);
    document.body.appendChild(button3);

})();
*/





// v2


(function() {
    'use strict';


    // Function to expand the menu
    function expandMenu() {
        return new Promise((resolve, reject) => {
            // Find the expand button
            // console.log("Rickscript - First Table start...");

            var expandButton = document.querySelector('.bolt-tree-expand-button');

            // Check if the button is found and it is in the collapsed state
            if (expandButton && expandButton.classList.contains('ms-Icon--ChevronRightMed')) {
                // Click on the button to expand the menu
                expandButton.click();
                resolve(); // Resolve the promise once the button is clicked
            } else {
                reject(new Error("Button not found or already expanded")); // Reject if the button is not found or already expanded
            }
        });
    }





      // Function to expand the second table
    function expandSecondTable() {
      return new Promise((resolve, reject) => {
        // Find the second table
        var secondTable = document.querySelectorAll('.bolt-table-container')[1];

        // console.log("Rickscript - Second Table start...");

        //console.log("Rickscript - Second Table:", secondTable);

        // Find the tbody of the second table
        var tbody = secondTable.querySelector('tbody.relative[hidden]');

        //console.log("Rickscript - TBody:", tbody);

        // Check if the tbody is found
        if (tbody) {
            // Remove the 'hidden' attribute to expand the table
            tbody.removeAttribute('hidden');
        }


        //console.log("Rickscript - Deploy Section - 1");

        // Find all span elements inside the second table
        var spans = secondTable.querySelectorAll('span');

        // Iterate over the span elements to find the one containing "Deploy"
        spans.forEach(function(span) {
            if (span.textContent.trim() === "Deploy") {
                var deploySection = span;
                //console.log("Rickscript - Deploy Section - 2:", deploySection);

                // Check if the "Deploy" section is collapsed
                var isCollapsed = deploySection.closest('tr').getAttribute('aria-expanded') === 'false';

                //console.log("Rickscript - IsCollapsed:", isCollapsed);

                // If collapsed, simulate a click event to expand the "Deploy" section
                if (isCollapsed) {
                    deploySection.closest('tr').click();
                    //console.log("Rickscript - Clicked to expand 'Deploy' section.");
                } else {
                    //console.log("Rickscript - The 'Deploy' section is already expanded.");
                }
            }
        });

        if (!deploySection) {
            //console.log("Rickscript - Couldn't find the 'Deploy' section.");
        }
      });
    }





    if (window.location.href.startsWith('https://dev.azure.com/') && window.location.href.includes('view=logs'))
    {

      // Call the expandMenu function when the page is fully loaded
      // window.addEventListener('load', expandMenu);

        window.addEventListener('load', function() {
            expandMenu()
                .then(() => {
                    // Code to execute after expanding the menu
                    // console.log("Rickscript xxxxx --- First menu expanded successfully!");
                    // Put the rest of your code here
                })
                .catch(error => {
                    // Handle errors if the button is not found or already expanded
                    // console.error("Rickscript xxxxx --- Error expanding first menu:", error.message);
                });
        });


      // Call the expandSecondTable function when the page is fully loaded
      window.addEventListener('load', function() {
          expandSecondTable()
              .then(() => {
                  // Code to execute after expanding the menu
                  // console.log("Rickscript xxxxx --- Second expanded successfully!");
                  // Put the rest of your code here
              })
              .catch(error => {
                  // Handle errors if the button is not found or already expanded
                  // console.error("Rickscript xxxxx --- Error expanding second menu:", error.message);
              });
      });





      // Find the Plan button
      var blahButtons = document.querySelectorAll('.bolt-link');

      if (blahButtons.length > 0) {

          // console.log('Rickscript --- expandMenu');

          // Iterate over each button to find the one with the text 'Plan'
              blahButtons.forEach(function(button) {
                if (button.textContent.trim() === 'Apply') {
                    // console.log('Rickscript --- Apply button found!');
                      button.click();
                }
                else if (button.textContent.trim() === 'Plan') {
                    // console.log('Rickscript --- Plan button found!');
                      button.click();
                }
              });
      } else {
          // console.error('Plan/Apply button not found.');
      }

    }






    // Function to add text to the search field
    function addTextToSearchField() {
        const searchButton = document.querySelector('button#__bolt-log-search');
        if (searchButton) {
            searchButton.click(); // Click on the search button
            // Wait for a short delay to ensure the search field is fully populated
            setTimeout(() => {
                const searchField = document.querySelector('.find-box input.bolt-textfield-input');

                if (searchField) {
                    // set the input value using the native setter
                    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                    nativeInputValueSetter.call(searchField, 'Terraform will perform the following actions:');

                    // Dispatch an input event to simulate user input
                    searchField.dispatchEvent(new Event('input', { bubbles: true }));
                }
            }, 1000); // Adjust delay time as needed
        }
    }

    // Function to append the buttons with CSS styling
    function appendButtons() {
        const button = createButton('Jump to Terraform action start', 'green', Terraform_action_start_handleButtonClick, 'jumpToTerraformActionStart', '900px');
        const button2 = createButton('Jump to Plan line', 'orange', Terraform_plan_line_handleButtonClick, 'jumpToPlanLine', '1113px');
        const button3 = createButton('Jump to No Changes', 'orange', Terraform_plan_no_changes_line_handleButtonClick, 'jumpToNoChanges', '1250px');
        document.body.appendChild(button);
        document.body.appendChild(button2);
        document.body.appendChild(button3);
    }

    // Function to create a button with specified properties
    function createButton(text, color, clickHandler, id, left) {
        const button = document.createElement('button');
        button.textContent = text;
        button.style.position = 'fixed';
        button.style.top = '80px'; // Adjusted top position
        button.style.left = left;
        button.style.zIndex = '9999';
        button.style.backgroundColor = color;
        button.setAttribute('data-button-id', id);
        button.addEventListener('click', clickHandler);
        return button;
    }

    // Function to remove the appended buttons
    function removeButtons() {
        const buttons = document.querySelectorAll('button[data-button-id="jumpToTerraformActionStart"], button[data-button-id="jumpToPlanLine"], button[data-button-id="jumpToNoChanges"]');
        buttons.forEach(button => button.remove());
    }

    // Function to expand the search button
    function expandSearchButton() {
        const searchButton = document.querySelector('button[data-is-focusable="true"][aria-label="Search phrases"]');
        if (searchButton) {
            setTimeout(() => {
                searchButton.click(); // Click on the search button to expand it
            }, 1200);
        }
    }

    // Function to handle button click event
    function Terraform_action_start_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'Terraform will perform the following actions:');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }

    // Function to handle button click event
    function Terraform_plan_line_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'Plan:');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }

    // Function to handle button click event
    function Terraform_plan_no_changes_line_handleButtonClick() {
        const searchField = document.querySelector('.find-box input.bolt-textfield-input');
        if (searchField) {
            searchField.focus(); // Focus on the search field
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            nativeInputValueSetter.call(searchField, 'No changes.');

            // Dispatch an input event to simulate user input
            searchField.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }

    // Call the initial setup when the script runs
    if (window.location.href.startsWith('https://dev.azure.com/') && window.location.href.includes('view=logs')) {
        // console.log("Rickscript - add buttons start!");
        expandMenu();
        expandSecondTable();

        appendButtons();
        addTextToSearchField();
    }





    // Debounce function
    function debounce(func, delay) {
        let timerId;
        return function(...args) {
            if (timerId) {
                clearTimeout(timerId);
            }
            timerId = setTimeout(() => {
                func.apply(this, args);
                timerId = null;
            }, delay);
        };
    }



    function executeScriptLogic() {
        // console.log("Rickscript --- Executing script logic...");
        let checkboxes = document.querySelectorAll('.primary-text');

        checkboxes.forEach(function(checkbox) {
            // console.log("Rickscript --- Checking checkbox:", checkbox.textContent);
            if (checkbox.textContent.includes('apply')) {
                // console.log("Rickscript --- Found checkbox with 'apply' in text:", checkbox);
                let checkboxParent = checkbox.closest('.queue-panel-list-row');
                let checkboxElement = checkboxParent.querySelector('.bolt-checkbox');

                if (checkboxElement) {
                    // console.log("Rickscript --- Checkbox element found:", checkboxElement);
                    if (checkboxElement.getAttribute('aria-checked') === 'true') {
                        // console.log("Rickscript --- Checkbox is checked, unticking...");
                        checkboxElement.click();
                    } else {
                        // console.log("Rickscript --- Checkbox is already unchecked.");
                    }
                } else {
                    // console.log("Rickscript --- Checkbox element not found!");
                }
            }
        });
    }



    // Function to wait for the panel "Stages to run" to be opened
    function waitForPanelToOpen() {
        const panelObserver = new MutationObserver(function(mutationsList, observer) {
            mutationsList.forEach(function(mutation) {
                // Check if the panel with the name "Stages to run" is added to the DOM
                if (mutation.addedNodes.length > 0) {
                    const panels = document.querySelectorAll('.bolt-header-title.title-m');
                    panels.forEach(function(panel) {
                        if (panel.textContent === 'Stages to run')
                        {
                            // Panel is found, now execute the necessary script logic
                            // console.log("Rickscript --- blah panel found");

                            setTimeout(function() {
                                executeScriptLogic();
                            }, 500);


                            // const currentUrl = window.location.href;
                            // observer.previousUrl = currentUrl;
                            // Disconnect the observer as it's no longer needed
                            observer.disconnect();

                        }else{

                            setTimeout(function() {
                                executeScriptLogic();
                            }, 1000);

                        }
                    });
                }
            });
        });

        // Start observing the document for changes in the DOM
        panelObserver.observe(document.documentElement, { childList: true, subtree: true });

        // Set a timeout to ensure the observer is disconnected if the panel is not found
        // setTimeout(function() {
        //     panelObserver.disconnect();
        //     // console.log("Rickscript --- Fuckkkkk");
        // }, 8000); // Adjust timeout as needed (in milliseconds)
    }








    if (window.location.href.includes('_build?definitionId') || window.location.href.includes('view=results'))
    {
        // console.log("Rickscript - ffucksjfkjskfs")
        waitForPanelToOpen();
    }






    // Function to handle URL changes
    function handleUrlChange(mutationsList, observer) {
        for (let mutation of mutationsList) {

            if (mutation.type === 'childList' || mutation.type === 'attributes') {

                const currentUrl = window.location.href;

                // console.log("Rickscript --- currentUrl --- ", currentUrl);
                // console.log("Rickscript --- observer.previousUrl --- ", observer.previousUrl);

                if (currentUrl !== observer.previousUrl)
                {

                    // console.log("Rickscript --- currentUrl --- ", currentUrl);
                    // console.log("Rickscript --- observer.previousUrl --- ", observer.previousUrl);

                    // observer.previousUrl = currentUrl;

                    if (!observer.previousUrl.includes('view=logs') && currentUrl.includes('view=logs')) {

                        // Transition from a URL without 'view=logs' to a URL with 'view=logs'
                        observer.previousUrl = currentUrl;

                        // console.log("Rickscript --- ******************* inside view=logs ******************* --- currentUrl --- ", currentUrl);

                        // Execute necessary functions
                        expandMenu();
                        expandSecondTable();
                        appendButtons();
                        addTextToSearchField();

                        // Find the Plan button
                        var blahButtons = document.querySelectorAll('.bolt-link');

                        if (blahButtons.length > 0) {
                            // Iterate over each button to find the one with the text 'Plan' or 'Apply'
                            blahButtons.forEach(function(button) {
                                if (button.textContent.trim() === 'Apply' || button.textContent.trim() === 'Plan') {
                                    button.click();
                                }
                            });
                        }


                        // console.log("Rickscript - URL change - GOOOOOOOOOOOOOOOOOOOOOOOOOD!");

                    } else if (observer.previousUrl.includes('view=logs') && !currentUrl.includes('view=logs')) {
                        // Transition from a URL with 'view=logs' to a URL without 'view=logs'
                        observer.previousUrl = currentUrl;

                        // If the URL doesn't match, remove buttons
                        removeButtons();


                    } else if (!observer.previousUrl.includes('_build?definitionId') && currentUrl.includes('_build?definitionId')) {
                    //} else if (currentUrl.includes('_build?definitionId')) {

                        // console.log("Rickscript - _build?definitionId");

                        //console.log("Rickscript --- currentUrl --- ", currentUrl);
                        //console.log("Rickscript --- observer.previousUrl --- ", observer.previousUrl);

                        observer.previousUrl = currentUrl;
                        waitForPanelToOpen();
                        // console.log("Rickscript --- _build?definitionId END !!!");


                    } else if (!observer.previousUrl.includes('view=results') && currentUrl.includes('view=results')) {
                    // } else if (currentUrl.includes('view=results')) {

                        // console.log("Rickscript - view=results");

                        //console.log("Rickscript --- currentUrl --- ", currentUrl);
                        //console.log("Rickscript --- observer.previousUrl --- ", observer.previousUrl);

                        observer.previousUrl = currentUrl;
                        waitForPanelToOpen();
                        // console.log("Rickscript --- view=results END !!!");

                    }

                }
                break;
            }
        }
    }







    // Debounce the handleUrlChange function with a delay of 100ms
//     const debouncedHandleUrlChange = debounce(handleUrlChange, 100);

//     // Create a MutationObserver
//     const observer = new MutationObserver((mutationsList) => {
//         debouncedHandleUrlChange(mutationsList, observer);
//     });


    // Start observing the DOM for URL changes
    const observer = new MutationObserver(handleUrlChange);
    observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true });
    observer.previousUrl = window.location.href;

})();