Automated MCM Population

Fetch package diff from pipeline heat-map and populate mcm.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Automated MCM Population
// @namespace   http://tampermonkey.net/
// @version     1.0
// @description Fetch package diff from pipeline heat-map and populate mcm.
// @author      adityamz@
// @match      	https://code.amazon.com/version-sets/*/revisions/*
// @match      	https://pipelines.amazon.com/pipelines/*/heat_map
// @match      	https://pipelines.amazon.com/pipelines/*
// @match      	https://mcm.amazon.com/cms/new*
// @match      	https://mcm.amazon.com/cms/*/edit*
// @require     https://code.jquery.com/jquery-3.4.1.min.js
// @grant       GM.xmlHttpRequest
// ==/UserScript==

(function() {

    //========== This part is run in code.amazon.com ==========
    function addNewPackages(packageName, packageUrl) {
        unsafeWindow.packageDetails.push({
            "packageName": packageName,
            "type": "Added",
            "diffUrl": packageUrl,
            "owners": [],
            "commits": []
        });
    }

    function addRemovedPackages(packagename, packageUrl) {
        unsafeWindow.packageDetails.push({
            "packageName": packagename,
            "type": "Removed",
            "diffUrl": packageUrl,
            "owners": [],
            "commits": []
        });
    }

    function addBranchChangePackages(packageName, branchName, oldBranchName, startHash, endHash, diffUrl) {
               const asciURL = "https://pipelines.amazon.com/diff/pkg_changes?package_name=" + packageName
        + "&branch_name=" + branchName + "&old_commit_id=" + startHash + "&new_commit_id=" + endHash;
        GM.xmlHttpRequest({
            method: "GET",
            url: asciURL,
            onload: function() {
                if (this.readyState === 4 && this.status === 200) {
                   
                   const changes = [];

                   let resElem = document.createElement("div");
                   resElem.innerHTML = this.responseText;
                   const commits = resElem.querySelectorAll('li');
                    commits.forEach(function(commit){
                        let author = commit.querySelector('.author>a').textContent;
                        let description = commit.querySelector('.description').textContent;
                        let repoChangeId = commit.querySelector('.change-link>a').href.split('/').reverse()[0];
                        changes.push({author,description,repoChangeId});
                    });

                    let ownerList = [];
                    const commitList = [];
                    changes.forEach(function(change) {
                        const author = change.author;
                        const commit = author + '@: [' + change.description.split('\n')[0] + '](https://code.amazon.com/packages/'
                        + packageName + '/commits/' + change.repoChangeId + ')';
                        ownerList.push(author);
                        commitList.push(commit);
                    });
                    ownerList = ownerList.filter((v, i, a) => a.indexOf(v) === i);
                    const changeType = "Branch Changed (" + oldBranchName + " => " + branchName + ")";
                    unsafeWindow.packageDetails.push({
                        "packageName": packageName,
                        "type": changeType,
                        "diffUrl": diffUrl,
                        "owners": ownerList,
                        "commits": commitList
                    });
                }
            }
        });
    }

    function addModifiedPackages(packageName, branchName, startHash, endHash, diffUrl) {


        const asciURL = "https://pipelines.amazon.com/diff/pkg_changes?package_name=" + packageName
        + "&branch_name=" + branchName + "&old_commit_id=" + startHash + "&new_commit_id=" + endHash;


        GM.xmlHttpRequest({
            method: "GET",
            url: asciURL,
            onload: function(response) {
                //console.log(this.responseText);
                if(this.status === 400){
                    console.log(this.responseJSON);
                   $("#toastDiff").text("Diff API Throttled.");
                   $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
                }
                if (this.readyState === 4 && this.status === 200) {

                    const changes = [];

                   let resElem = document.createElement("div");
                   resElem.innerHTML = this.responseText;
                   const commits = resElem.querySelectorAll('li');
                    commits.forEach(function(commit){
                        let author = commit.querySelector('.author>a').textContent;
                        let description = commit.querySelector('.description').textContent;
                        let repoChangeId = commit.querySelector('.change-link>a').href.split('/').reverse()[0];
                        changes.push({author,description,repoChangeId});
                    });
           
                    console.log("fetched details for :"+ packageName);
                    let ownerList = [];
                    const commitList = [];
                    changes.forEach(function(change) {
                        const author = change.author;
                        const commit = author + '@: [' + change.description.split('\n')[0] + '](https://code.amazon.com/packages/'
                        + packageName + '/commits/' + change.repoChangeId + ')';
                        ownerList.push(author);
                        commitList.push(commit);
                    });
                    ownerList = ownerList.filter((v, i, a) => a.indexOf(v) === i);
                    unsafeWindow.packageDetails.push({
                        "packageName": packageName,
                        "type": "Source Changed",
                        "diffUrl": diffUrl,
                        "owners": ownerList,
                        "commits": commitList
                    });
                }
            }
        });
    }

    function getPackageDiffOwners() {
        let allOwners = [];
        let diffOwnersString = "";
        let isPackageChecked = false;
        unsafeWindow.packageDetails.forEach(pkgDetail =>  {
            isPackageChecked = $("#" + pkgDetail.packageName + "Check").is(":checked");
            if (isPackageChecked) {
                diffOwnersString = diffOwnersString + "* [" + pkgDetail.packageName + "](https://code.amazon.com"
                    + pkgDetail.diffUrl + ") [" + pkgDetail.type + "]\n";
                pkgDetail.owners.forEach(owner => {
                    diffOwnersString = diffOwnersString + "  * " + owner + "\n";
                    allOwners.push(owner);
                })
            }
        })
        if (allOwners.length > 0) {
            allOwners = allOwners.filter((v, i, a) => a.indexOf(v) === i);
            diffOwnersString = diffOwnersString + "* All Owners\n";
            allOwners.forEach(owner => {
                diffOwnersString = diffOwnersString + "  * " + owner + "\n";
            })
        }
        unsafeWindow.commitOwners=JSON.stringify(allOwners)
        return copyToClipboard(diffOwnersString);
    }

    function getPackageDiffCommits() {
        let diffCommitsString = "";
        let isPackageChecked = false;
        unsafeWindow.packageDetails.forEach(pkgDetail =>  {
            isPackageChecked = $("#" + pkgDetail.packageName + "Check").is(":checked");
            if (isPackageChecked) {
                diffCommitsString = diffCommitsString + "* [" + pkgDetail.packageName + "](https://code.amazon.com"
                    + pkgDetail.diffUrl + ") [" + pkgDetail.type + "]\n";
                pkgDetail.commits.forEach(commit => {
                    diffCommitsString = diffCommitsString + "  * " + commit + "\n";
                })
            }

        })

        unsafeWindow.commitChanges = diffCommitsString;

        let pipelineDescription = getPipelineDescription(unsafeWindow.pipelineDetails, unsafeWindow.vsDetails)
        let commitDetailsDescription = getCommitDetailsDescription(diffCommitsString)

        return copyToClipboard(pipelineDescription + '\n'+commitDetailsDescription);
    }

    function copyToClipboard(text) {
        if (text !== "") {
            $("#toastDiff").text("Copied to clipboard!");
            $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
            const textarea = document.createElement("textarea");
            textarea.textContent = text;
            textarea.style.position = "fixed";
            document.body.appendChild(textarea);
            textarea.select();
            try {
                document.execCommand("copy");
            } catch (ex) {
                console.warn("Copy to clipboard failed.", ex);
            } finally {
                document.body.removeChild(textarea);
                return true;
            }
        } else {
            $("#toastDiff").text("No diff found! Try again.");
            $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
            return false;
        }
    }

    unsafeWindow.packageDetails = [];
    unsafeWindow.pipelineDetails = {pipelineName:'',packages:[]};

    $('#packages tr.added-pkg').each(function(i, el) {
        const $tds = $(this).find("td");
        const packageUrl = $tds.eq(0).find("a").attr("href");
        const packageName = $tds.eq(0).text();
        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("id", (packageName + "Check"));
        checkbox.setAttribute("style", "margin-right:5px");
        $tds.eq(0).prepend(checkbox);
        addNewPackages(packageName, packageUrl);
    })

    $('#packages tr.error').each(function(i, el) {
        const $tds = $(this).find('td');
        const packageUrl = $tds.eq(0).find('a').attr('href');
        const packageName = $tds.eq(0).text();
        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("id", (packageName + "Check"));
        checkbox.setAttribute("style", "margin-right:5px");
        $tds.eq(0).prepend(checkbox);
        addRemovedPackages(packageName, packageUrl);
    })

    $('#packages tr.branch-change').each(function(i, el) {
        const $tds = $(this).find('td');
        const diffUrl = $tds.eq(5).find('a').attr('href').trim();
        const startHash = $tds.eq(1).find('a').attr('href').split('/')[4].trim();
        const endHash = $tds.eq(3).find('a').attr('href').split('/')[4].trim();
        const packageName = $tds.eq(0).text().trim();
        const oldBranchName = $tds.eq(6).text().split("->")[0].trim();
        const branchName = $tds.eq(6).text().split("->")[1].trim();
        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("id", (packageName + "Check"));
        checkbox.setAttribute("style", "margin-right:5px");
        $tds.eq(0).prepend(checkbox);

        $(checkbox).change(function(){
            if(this.checked){
                addBranchChangePackages(packageName, branchName, oldBranchName, startHash, endHash, diffUrl);
            }

        })


    })

    $('#packages tr.source-change').each(function(i, el) {
        const $tds = $(this).find('td');
        const diffUrl = $tds.eq(5).find('a').attr('href').trim();
        const startHash = $tds.eq(1).find('a').attr('href').split('/')[4].trim();
        const endHash = $tds.eq(3).find('a').attr('href').split('/')[4].trim();
        const packageName = $tds.eq(0).text().trim();
        const branchName = $tds.eq(6).text().trim();
        const checkbox = document.createElement("input");
        checkbox.setAttribute("type", "checkbox");
        checkbox.setAttribute("id", (packageName + "Check"));
        checkbox.setAttribute("style", "margin-right:5px");
        $tds.eq(0).prepend(checkbox);
        $(checkbox).change(function(){

            if(this.checked){
                addModifiedPackages(packageName, branchName, startHash, endHash, diffUrl);
            }


        })

    })


    function createMCMWithTemplate(mcm_id){
        let diffcommit = getPackageDiffCommits();
        let diffpackage = getPackageDiffOwners();

        window.location.href = "https://mcm.amazon.com/cms/new?from_template="+mcm_id+"&commitChanges="+encodeURIComponent(unsafeWindow.commitChanges)+"&commitOwners="+unsafeWindow.commitOwners+
        "&vsDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.vsDetails)) + "&pipelineDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.pipelineDetails));
       
    }

    function updateMCMWithExistingDraft(mcm_id){
       let diffcommit = getPackageDiffCommits();
        let diffpackage = getPackageDiffOwners();

        window.location.href = "https://mcm.amazon.com/cms/"+mcm_id+"/edit?commitChanges="+encodeURIComponent(unsafeWindow.commitChanges)+"&commitOwners="+unsafeWindow.commitOwners+
        "&vsDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.vsDetails)) + "&pipelineDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.pipelineDetails));
    }

    const vsRangeDiff = document.getElementById('vs_range_diff');
    if (vsRangeDiff) {
        const mainDiv = document.createElement('div');
        mainDiv.innerHTML = '<div><div style="overflow: visible;"><div class="text-right top-buffer-small" style="background: aliceblue;padding: 8px;filter: drop-shadow(2px 2px 2px black);"><div> <button type="button" class="btn btn-default" id="diffCommits"  style="width: 48%;float: left;">Copy Diff Commits</button> <button type="button" class="btn btn-default" id="diffOwners" style=" width: 48%;">Copy Diff Owners</button><input id="mcm_id" type="input" style="width: 100%;margin-top: 10px" placeholder="MCM template ID eg: MCM-78603254 or leave empty to use default template"/><button type="button" disabled class="btn btn-default" id="createMCM"  style="width: 100%;margin-top: 10px">Loading ...</button></div></div><div id="toastDiff" style="width: 180px; background-color: rgba(0, 0, 0, 0.8); height: 30px; position: absolute; margin: 20px 20px 20px 6%; border-radius: 50px; color: rgba(255, 255, 255, 0.8);text-align: center;padding-top: 6px; display: none;"></div></div></div>';
        vsRangeDiff.appendChild(mainDiv);

        unsafeWindow.pipelineDetails = JSON.parse(new URLSearchParams(window.location.search).get('pipelineDetails'));
        unsafeWindow.vsDetails = JSON.parse(new URLSearchParams(window.location.search).get('vsDetails'));

        //mark all the packages autobuilt in the pipeline as checked for diff creation
        window.addEventListener("load", ()=>{
            unsafeWindow.pipelineDetails.packages.forEach((pkg)=>{
                let pkgElements = document.querySelectorAll('#'+pkg+'Check');
                pkgElements.forEach((pkgElem)=>{
                     $(pkgElem).click();
                })
            });

            $('#createMCM').text('Create MCM');
            $('#createMCM').prop('disabled',false);

        });


        // adding click listener for diff commits
        $( "#diffCommits" ).click(function() {
            getPackageDiffCommits();
        });

        $( "#diffOwners" ).click(function() {
            getPackageDiffOwners();
        });

        $( "#createMCM" ).click(function() {
            const mcm_id = $('#mcm_id').val();
            if(mcm_id){
                updateMCMWithExistingDraft(mcm_id);
            }
            else {
              createMCMWithTemplate('7e9a85a5-bd0a-4560-8379-00a73ef82f1b');
            }  
        });


    }


    //========== This part is run in pipelines.amazon.com ==========
    function getVSDiffLink(href) {
        const splittedHref = href.split("&");
        const currVS = splittedHref[0].split("=")[1];
        const currRev = splittedHref[1].split("=")[1];
        const prevVS = splittedHref[3].split("=")[1];
        const prevRev = splittedHref[4].split("=")[1];
        const vsDetails = {};
        vsDetails.cur = currRev;
        vsDetails.prev = prevRev;
        vsDetails.name = prevVS;


        return "https://code.amazon.com/version-sets/" + currVS + "/revisions/" + currRev + "?previous=" + prevRev + "&previous_vs=" + prevVS +
            "&pipelineDetails=" + sessionStorage.getItem('pipelineDetails') +
            "&vsDetails=" + JSON.stringify(vsDetails);
    }

    const pipelineName = document.querySelector('.pipeline-name-header');

    if(pipelineName){
     unsafeWindow.pipelineDetails.pipelineName = pipelineName.innerText;
    }



    const packages = document.querySelector('#stage-name-Packages > div > h3 > span');
    if(packages){
        document.querySelectorAll('.autobuild .target-details > div.name').forEach((pkg)=>{
            let pkg_name = pkg.getAttribute('data-targetname').split('/')[0]
            unsafeWindow.pipelineDetails.packages.push(pkg_name)

        })

        sessionStorage.setItem("pipelineDetails",JSON.stringify(unsafeWindow.pipelineDetails))

    }

    const diffLinkElements = document.getElementsByClassName("diff-link");
    if (diffLinkElements && !packages) {
        for(let i=0; i < diffLinkElements.length; i++) {
            const element = diffLinkElements[i];
            const href = element.getElementsByTagName("a")[0].getAttribute("href");
            const vsDiffLink = getVSDiffLink(href);
            const aTag = document.createElement("a");
            aTag.setAttribute("href", vsDiffLink);
            aTag.innerText = "VS";
            element.appendChild(aTag);
        }
    }

    //this code part will run in mcm

    const insideMcm = $('.mcm-logo').length


    function getPipelineDescription(pipelineDetails, vsDetails){

        let pipelineDescription = ""

        pipelineDescription+= `## ${pipelineDetails.pipelineName}\n`
        pipelineDescription+= `- Pipeline Link - https://pipelines.amazon.com/pipelines/${pipelineDetails.pipelineName}\n`
        pipelineDescription+= `- Pipeline Diff -  [Link](https://pipelines.amazon.com/diff?new_name=${vsDetails.name}&new_revision=${vsDetails.cur}&new_type=VS&old_name=${vsDetails.name}&old_revision=${vsDetails.prev}&old_type=VS)\n`
        pipelineDescription+= `- Current VFI - ${vsDetails.name}@B${vsDetails.prev}\n`
        pipelineDescription+= `- VFI to be Deployed - ${vsDetails.name}@B${vsDetails.cur}\n`

        return pipelineDescription;





    }

    function getCommitDetailsDescription(commitDetails){

    let commitChanges = "";
        if(commitDetails){
          commitChanges+="#### Changes to be promoted in prod:\n"
          commitChanges+=commitDetails;
        }

        return commitChanges;
    }

    function updateDescription(commitDetails, vsDetails, pipelineDetails){
        const curr = $('#description').val()
        const rollbackId = "## Rollback Procedure";

        const pipelineDescription=getPipelineDescription(pipelineDetails, vsDetails);
        console.log(pipelineDescription);

        const pos = curr.indexOf('#### What is the purpose of this activity or change?')<0?curr.indexOf('Activity Details'):curr.indexOf('#### What is the purpose of this activity or change?');

        let commitChanges = getCommitDetailsDescription(commitDetails);

        let data = curr.substr(0, pos) + pipelineDescription+'\n'+ commitChanges+'\n' + curr.substr(pos);

        
        
        $('#description').val(data)

    }

    function updateMCMTitle(pipelineDetails){
        const title = $('#title')
        title.val(title.val()+pipelineDetails.pipelineName)

    }

    function addcommitOwners(commitOwners) {

        commitOwners.forEach((owner)=>{
            $('#add_approver_login').val(owner)
            $('#add_approver').click()
        })

    }

    if(insideMcm>0){
        const data = new URLSearchParams(window.location.search);
        const commitDetails = decodeURIComponent(data.get('commitChanges'));
        const versionSetDetails = JSON.parse(decodeURIComponent(data.get('vsDetails')));
        const pipelineDetails = JSON.parse(decodeURIComponent(data.get('pipelineDetails')));
        console.log(pipelineDetails);
        console.log(versionSetDetails);
        console.log(commitDetails);
        const commitOwners = JSON.parse(data.get('commitOwners'));
        updateMCMTitle(pipelineDetails);

        if(versionSetDetails){
            updateDescription(commitDetails, versionSetDetails, pipelineDetails)
        }
        if(commitOwners && commitOwners.length>0){
            addcommitOwners(commitOwners);
        }
    }

})();