// ==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);
}
}
})();