MMS IAM Pull Request template

A small script intended to apply a Cloud IAM team scoped Github PR template when creating new pull requests against the 10gen/mms repo

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MMS IAM Pull Request template
// @namespace    https://www.mongodb.com/
// @version      1.4
// @updateUrl    https://greasyfork.org/scripts/440137-mms-iam-pull-request-template/code/MMS%20IAM%20Pull%20Request%20template.user.js
// @description  A small script intended to apply a Cloud IAM team scoped Github PR template when creating new pull requests against the 10gen/mms repo
// @author       [email protected]
// @match        https://github.com/10gen/mms/compare/*
// @icon         https://www.google.com/s2/favicons?domain=mongodb.com
// @grant        none
// ==/UserScript==
// Happy Skunkworks! This a test comment for script autoupdating via TamperMonkey
// You should be automatically updated to version 1.3

/** Github base PR creation page element IDs **/
const PR_FORM_ELEMENT_ID = "new_pull_request";
const PR_TITLE_ELEMENT_ID = "pull_request_title";
const PR_BODY_ELEMENT_ID = "pull_request_body";

/** Regex **/
// matches CLOUDP-{any digits}
const CLOUD_JIRA_TICKET_REGEX = /CLOUDP-\d+/;
// matches CLOUDP-{any digits}:{any characters}
const PR_TITLE_REGEX = /CLOUDP-\d+:.*/;
// matches https://jira.mongodb.org/browse/CLOUDP-{any digits}
const CONTAINS_JIRA_URL_REGEX = /.*https:\/\/jira\.mongodb\.org\/browse\/CLOUDP-\d+.*/;

/** Custom UI elements **/
function createFillTemplateButton(text, onClick) {
    // https://www.mongodb.design/component/palette/example/
    const MONGO_GREEN_BASE = "#13AA52";
    const MONGO_GREEN_DARK_2 = "#116149";

    // Create button and disable default behavior (form submission) in favor of provided onclick
    const button = document.createElement("button");
    button.type = "button";
    button.innerHTML = "Prefill PR Template";
    button.onclick = fillPrUsingTemplate;

    // Default styling
    button.style.display = "inline-block";
    button.style.color = "white";
    button.style.backgroundColor = MONGO_GREEN_BASE;
    button.style.border = "0.16em solid";
    button.style.borderRadius = "2em";
    button.style.borderColor = "white";
    button.style.padding = "0.3em 1.2em";
    button.style.margin = "1em 0em";
    button.style.textAlign = "center";
    button.style.fontSize = "1.12em";

    // Dynamic styling
    button.addEventListener("mouseenter", (e) => {
        button.style.backgroundColor = MONGO_GREEN_DARK_2;
    });

    button.addEventListener("mouseleave", (e) => {
        button.style.backgroundColor = MONGO_GREEN_BASE;
    });

    return button;
}

function createPrWarningElement(alertText, tooltipText) {
    // https://www.mongodb.design/component/palette/example/
    const MONGO_YELLOW_BASE = "#FFDD49";
    const MONGO_YELLOW_LIGHT_2 = "#FEF2C8";
    const MONGO_YELLOW_DARK_3 = "#543E07";

    const alert = document.createElement("div");

    // Default styling
    alert.style.display = "inline-block";
    alert.style.position = "relative"; // for anchoring the tooltip
    alert.style.color = MONGO_YELLOW_DARK_3;
    alert.style.backgroundColor = MONGO_YELLOW_LIGHT_2;
    alert.style.border = "0.05em solid";
    alert.style.borderRadius = "2em";
    alert.style.borderColor = MONGO_YELLOW_BASE;
    alert.style.padding = "0.3em 1.2em";
    alert.style.margin = "8px 8px";
    alert.style.textAlign = "center";
    alert.style.fontSize = ".87em";

    // Tooltip
    const tooltip = document.createElement("span");
    tooltip.innerHTML = `<a target="_blank" href="https://wiki.corp.mongodb.com/display/MMS/Cloud+Team+Code+Review+Guidelines">${tooltipText}</a>`;
    tooltip.style.display = "none"; // hide by default
    tooltip.style.zIndex = "10";
    tooltip.style.backgroundColor = "white";
    tooltip.style.color = "black";
    tooltip.style.border = "0.05em solid";
    tooltip.style.borderRadius = "8px";
    tooltip.style.borderColor = MONGO_YELLOW_BASE;
    tooltip.style.textAlign = "center";
    tooltip.style.position = "absolute";
    tooltip.style.padding = "12px";
    // center horizontally above the alert
    tooltip.style.left = "50%";
    tooltip.style.transform = "translateX(-50%)";
    tooltip.style.bottom = "100%";
    tooltip.style.width = "300px";
    tooltip.style.transition = ".3s opacity";

    alert.append(alertText);
    alert.append(tooltip);

    alert.addEventListener("mouseenter", (e) => {
        tooltip.style.display = "inline-block";
    });

    alert.addEventListener("mouseleave", (e) => {
        tooltip.style.display = "none";
    });

    return alert;
}

function createWarningAlertsContainer(...children) {
    const warningElementsContainer = document.createElement("div");
    warningElementsContainer.style.width = "100%";
    warningElementsContainer.style.display = "flex";
    warningElementsContainer.style.flexDirection = "row";
    warningElementsContainer.style.justifyContent = "flex-end";
    for (const child of children) {
        warningElementsContainer.appendChild(child);
    }
    return warningElementsContainer;
}

const PR_TITLE_INVALID_WARNING_ELEMENT = createPrWarningElement("Invalid PR Title", "PR Title must begin with a CLOUDP JIRA ticket ID, followed by a colon and space, then a description of your changes i.e. `CLOUDP-12345: Yay!`");
const PR_BODY_INVALID_WARNING_ELEMENT = createPrWarningElement("Invalid PR Body", "PR Body must contain a link to the relevant CLOUDP JIRA ticket's URL");

/** UI triggered functions **/
function generatePullRequestBodyMarkdown(jiraTicketUrl, prevTitle, prevBody) {
    const template =
`JIRA Ticket: ${jiraTicketUrl}

### Preflight Steps
* [ ] The PR title is of format '{TICKET_ID}: Description of changes', with the correct CLOUDP-XXXXX ticket as the TICKET_ID
* [ ] The link to the JIRA ticket above is correct
* [ ] I've reviewed my own code

### Description
* Hooray, my change is ready!

### QA Steps Taken
* Unit/Integ/Manual testing done
* Relevant screenshots, if applicable`

    let body = template;
    
    // If there exists previous input from the user, prepend it to the body such that no work is lost
    if (prevBody) {
        body = `Previous Body:\n${prevBody}\n\n` + body;
    }

    if (prevTitle) {
        body = `Previous Title:\n${prevTitle}\n\n` + body;
    }

    return body;
}

function fillPrUsingTemplate() {
    
    // Try to yoink the CLOUDP ticket id from the branch name, derived from the current URL
    // If none found, default to a '{TICKET_ID}' string
    const href = window.location.href;
    const hrefComponents = href.split("/");
    const branchName = hrefComponents[hrefComponents.length - 1].split("?")[0];
    const isBranchNameJiraTicket = CLOUD_JIRA_TICKET_REGEX.test(branchName);
    const jiraTicketId = isBranchNameJiraTicket ? branchName : "{TICKET_ID}";
    
    // Set PR title
    const prTitleInput = document.getElementById(PR_TITLE_ELEMENT_ID);
    const prevTitle = prTitleInput.value;

    const prefilledPrTitle = `${jiraTicketId}: {Description of Changes}`;
    prTitleInput.value = prefilledPrTitle;

    // Set PR Body
    const jiraTicketUrl = `https://jira.mongodb.org/browse/${jiraTicketId}`;
    const prBodyTextArea = document.getElementById(PR_BODY_ELEMENT_ID);
    const prevBody = prBodyTextArea.value;

    prBodyTextArea.value = generatePullRequestBodyMarkdown(jiraTicketUrl, prevTitle, prevBody);
    
    // Validate results of prefill
    validatePrTitle(prTitleInput.value);
    validatePrBody(prBodyTextArea.value);
}

function validatePrTitle(newTitle) {
    if (!PR_TITLE_REGEX.test(newTitle)) {
        PR_TITLE_INVALID_WARNING_ELEMENT.style.display = "inline-block";
    } else {
        PR_TITLE_INVALID_WARNING_ELEMENT.style.display = "none";
    }
}


function validatePrBody(newBody) {
    if (!CONTAINS_JIRA_URL_REGEX.test(newBody)) {
        PR_BODY_INVALID_WARNING_ELEMENT.style.display = "inline-block";
    } else {
        PR_BODY_INVALID_WARNING_ELEMENT.style.display = "none";
    }
}

// "main" immediately executed function
(function() {
    'use strict';
    // Register listeners for PR title and body input elements
    const prTitleInput = document.getElementById(PR_TITLE_ELEMENT_ID);
    const prBodyTextArea = document.getElementById(PR_BODY_ELEMENT_ID);

    prTitleInput.addEventListener('input', (event) => {
        validatePrTitle(event.target.value);
    });
    prTitleInput.addEventListener('change', (event) => {
        validatePrTitle(event.target.value);
    });

    prBodyTextArea.addEventListener('input', (event) => {
        validatePrBody(event.target.value);
    });

    prBodyTextArea.addEventListener('change', (event) => {
        validatePrBody(event.target.value);
    });

    // Inject custom elements into the page
    const formElement = document.getElementById(PR_FORM_ELEMENT_ID);
    formElement.insertBefore(createFillTemplateButton(), formElement.childNodes[0]);

    const tabContainerElement = formElement.getElementsByTagName("tab-container")[0];
    tabContainerElement.insertAdjacentElement("afterend", createWarningAlertsContainer(PR_TITLE_INVALID_WARNING_ELEMENT, PR_BODY_INVALID_WARNING_ELEMENT));
})();