GitLab auto reviewers

Automatically add reviewers to GitLab merge requests

// ==UserScript==
// @name         GitLab auto reviewers
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Automatically add reviewers to GitLab merge requests
// @author       Mohammad Sh
// @match        https://gitlab.com/*/*/-/merge_requests/*/*
// @match        https://gitlab.com/*/*/-/merge_requests/new*
// @match        https://gitlab.com/*/merge_requests/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gitlab.com
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // ===== Configuration =====
    
    // List of reviewers to add (usernames)
    const backendReviewers = [
        'mshabibR365',       
    ];
    
    // Timing configuration (in milliseconds)
    const TIMING = {
        SHORT_DELAY: 200,      // Short delay for UI updates
        MAX_WAIT_TIME: 5000,   // Maximum wait time for search results
    };

    // CSS selectors for GitLab UI elements
    const SELECTORS = {
        REVIEWER_BLOCK: '.block.reviewer',
        DROPDOWN: '.reviewers-dropdown.gl-ml-auto.gl-new-dropdown',
        DROPDOWN_BUTTON: 'button',
        SEARCH_INPUT: '.gl-listbox-search-input',
        DROPDOWN_ITEM: '.gl-new-dropdown-item',
        USERNAME_ELEMENT: '.gl-text-subtle',
        ASSIGNEE_BLOCK: '.block.assignee'
    };

    // ===== Helper Functions =====
    
    /**
     * Sleep for a specified amount of time
     * @param {number} ms - Time to sleep in milliseconds
     * @returns {Promise} - Promise that resolves after the specified time
     */
    const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    
    /**
     * Log a message with a timestamp
     * @param {string} message - Message to log
     * @param {string} type - Log type (log, error, warn)
     */
    const log = (message, type = 'log') => {
        const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
        console[type](`[${timestamp}] GitLab Auto Reviewers: ${message}`);
    };
    
    /**
     * Find an element in the DOM
     * @param {string} selector - CSS selector
     * @returns {Element|null} - Found element or null
     */
    const findElement = (selector) => {
        const element = document.querySelector(selector);
        if (!element) {
            log(`Element not found: ${selector}`, 'error');
        }
        return element;
    };
    
    /**
     * Click on the reviewers dropdown button
     * @returns {Promise<boolean>} - True if successful, false otherwise
     */
    const clickReviewersDropdown = async () => {
        const dropdown = document.querySelector(SELECTORS.DROPDOWN);
        if (!dropdown) {
            log('Reviewers dropdown not found', 'error');
            return false;
        }
        
        const button = dropdown.querySelector(SELECTORS.DROPDOWN_BUTTON);
        if (!button) {
            log('Dropdown button not found', 'error');
            return false;
        }
        
        button.click();
        await sleep(TIMING.SHORT_DELAY);
        return true;
    };
    
    /**
     * Search for a reviewer in the dropdown
     * @param {string} reviewer - Reviewer username to search for
     * @returns {Promise<boolean>} - True if successful, false otherwise
     */
    const searchForReviewer = async (reviewer) => {
        // Find the search input
        const searchInput = findElement(SELECTORS.SEARCH_INPUT);
        if (!searchInput) return false;
        
        // Clear any previous search
        searchInput.value = '';
        searchInput.dispatchEvent(new Event('input', { bubbles: true }));
        await sleep(TIMING.SHORT_DELAY);
        
        // Enter the reviewer name
        searchInput.value = reviewer;
        searchInput.dispatchEvent(new Event('input', { bubbles: true }));
        
        return true;
    };
    
    /**
     * Find and click on a reviewer in the dropdown
     * @param {string} reviewer - Reviewer username to find
     * @returns {Promise<boolean>} - True if found and clicked, false otherwise
     */
    const findAndClickReviewer = async (reviewer) => {
        const startTime = Date.now();
        
        while (Date.now() - startTime < TIMING.MAX_WAIT_TIME) {
            const listItems = document.querySelectorAll(SELECTORS.DROPDOWN_ITEM);
            
            for (const item of listItems) {
                const nameElement = item.querySelector(SELECTORS.USERNAME_ELEMENT);
                
                if (nameElement && nameElement.textContent.trim().toLowerCase().includes(reviewer.toLowerCase())) {
                    log(`Found reviewer: ${reviewer}`);
                    item.click();
                    return true;
                }
            }
            
            // Wait 100ms before trying again
            await sleep(100);
        }
        
        log(`Reviewer not found: ${reviewer}`, 'error');
        return false;
    };
    
    /**
     * Add a single reviewer
     * @param {string} reviewer - Reviewer username to add
     * @returns {Promise<boolean>} - True if successful, false otherwise
     */
    const addReviewer = async (reviewer) => {
        try {
            
            // Search for the reviewer
            if (!await searchForReviewer(reviewer)) return false;
            
            // Find and click on the reviewer
            return await findAndClickReviewer(reviewer);
        } catch (error) {
            log(`Error adding reviewer ${reviewer}: ${error.message}`, 'error');
            return false;
        }
    };
    
    /**
     * Add multiple reviewers
     * @param {string[]} reviewers - List of reviewer usernames to add
     * @returns {Promise<void>}
     */
    const addReviewers = async (reviewers) => {
        log(`Starting to add ${reviewers.length} reviewers`);
        
        for (const reviewer of reviewers) {
            await addReviewer(reviewer);
        }
        
        // Close the dropdown
        await clickReviewersDropdown();       
        const assigneeBlock = findElement(SELECTORS.ASSIGNEE_BLOCK);
        if (assigneeBlock) {
            assigneeBlock.click();
        }
        
        log('Finished adding reviewers');
    };
    
    /**
     * Create a button to add reviewers
     * @param {string} repoName - Name to display on the button
     * @param {string[]} reviewers - List of reviewer usernames to add
     * @param {string} backgroundColor - Button background color
     * @param {string} textColor - Button text color
     * @returns {HTMLButtonElement} - Created button
     */
    const createButton = (repoName, reviewers, backgroundColor, textColor) => {
        const button = document.createElement("button");
        button.innerHTML = `Add ${repoName} reviewers`;
        button.style = `
            background: ${backgroundColor}; 
            color: ${textColor}; 
            margin: 1em;
            padding: 0.5em 1em;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
        `;
        
        button.onclick = (event) => {
            event.stopPropagation();
            event.preventDefault();
            
            addReviewers(reviewers).catch(err => {
                log(`Error in add reviewers process: ${err.message}`, 'error');
            });
        };
        
        return button;
    };
    
    // ===== Main Initialization =====
    
    // Find the reviewer block
    const reviewerBlock = findElement(SELECTORS.REVIEWER_BLOCK);
    if (!reviewerBlock) {
        log('Reviewer block not found, script will not run', 'error');
        return;
    }
    
    // Create and add the button
    const backendReviewersButton = createButton('', backendReviewers, 'lime', 'black');
    reviewerBlock.appendChild(backendReviewersButton);
    
    log('GitLab Auto Reviewers script initialized');
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址