Automated MCM Population

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

  1. // ==UserScript==
  2. // @name Automated MCM Population
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Fetch package diff from pipeline heat-map and populate mcm.
  6. // @author adityamz@
  7. // @match https://code.amazon.com/version-sets/*/revisions/*
  8. // @match https://pipelines.amazon.com/pipelines/*/heat_map
  9. // @match https://pipelines.amazon.com/pipelines/*
  10. // @match https://mcm.amazon.com/cms/new*
  11. // @match https://mcm.amazon.com/cms/*/edit*
  12. // @require https://code.jquery.com/jquery-3.4.1.min.js
  13. // @grant GM.xmlHttpRequest
  14. // ==/UserScript==
  15.  
  16. (function() {
  17.  
  18. //========== This part is run in code.amazon.com ==========
  19. function addNewPackages(packageName, packageUrl) {
  20. unsafeWindow.packageDetails.push({
  21. "packageName": packageName,
  22. "type": "Added",
  23. "diffUrl": packageUrl,
  24. "owners": [],
  25. "commits": []
  26. });
  27. }
  28.  
  29. function addRemovedPackages(packagename, packageUrl) {
  30. unsafeWindow.packageDetails.push({
  31. "packageName": packagename,
  32. "type": "Removed",
  33. "diffUrl": packageUrl,
  34. "owners": [],
  35. "commits": []
  36. });
  37. }
  38.  
  39. function addBranchChangePackages(packageName, branchName, oldBranchName, startHash, endHash, diffUrl) {
  40. const asciURL = "https://pipelines.amazon.com/diff/pkg_changes?package_name=" + packageName
  41. + "&branch_name=" + branchName + "&old_commit_id=" + startHash + "&new_commit_id=" + endHash;
  42. GM.xmlHttpRequest({
  43. method: "GET",
  44. url: asciURL,
  45. onload: function() {
  46. if (this.readyState === 4 && this.status === 200) {
  47. const changes = [];
  48.  
  49. let resElem = document.createElement("div");
  50. resElem.innerHTML = this.responseText;
  51. const commits = resElem.querySelectorAll('li');
  52. commits.forEach(function(commit){
  53. let author = commit.querySelector('.author>a').textContent;
  54. let description = commit.querySelector('.description').textContent;
  55. let repoChangeId = commit.querySelector('.change-link>a').href.split('/').reverse()[0];
  56. changes.push({author,description,repoChangeId});
  57. });
  58.  
  59. let ownerList = [];
  60. const commitList = [];
  61. changes.forEach(function(change) {
  62. const author = change.author;
  63. const commit = author + '@: [' + change.description.split('\n')[0] + '](https://code.amazon.com/packages/'
  64. + packageName + '/commits/' + change.repoChangeId + ')';
  65. ownerList.push(author);
  66. commitList.push(commit);
  67. });
  68. ownerList = ownerList.filter((v, i, a) => a.indexOf(v) === i);
  69. const changeType = "Branch Changed (" + oldBranchName + " => " + branchName + ")";
  70. unsafeWindow.packageDetails.push({
  71. "packageName": packageName,
  72. "type": changeType,
  73. "diffUrl": diffUrl,
  74. "owners": ownerList,
  75. "commits": commitList
  76. });
  77. }
  78. }
  79. });
  80. }
  81.  
  82. function addModifiedPackages(packageName, branchName, startHash, endHash, diffUrl) {
  83.  
  84.  
  85. const asciURL = "https://pipelines.amazon.com/diff/pkg_changes?package_name=" + packageName
  86. + "&branch_name=" + branchName + "&old_commit_id=" + startHash + "&new_commit_id=" + endHash;
  87.  
  88.  
  89. GM.xmlHttpRequest({
  90. method: "GET",
  91. url: asciURL,
  92. onload: function(response) {
  93. //console.log(this.responseText);
  94. if(this.status === 400){
  95. console.log(this.responseJSON);
  96. $("#toastDiff").text("Diff API Throttled.");
  97. $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
  98. }
  99. if (this.readyState === 4 && this.status === 200) {
  100.  
  101. const changes = [];
  102.  
  103. let resElem = document.createElement("div");
  104. resElem.innerHTML = this.responseText;
  105. const commits = resElem.querySelectorAll('li');
  106. commits.forEach(function(commit){
  107. let author = commit.querySelector('.author>a').textContent;
  108. let description = commit.querySelector('.description').textContent;
  109. let repoChangeId = commit.querySelector('.change-link>a').href.split('/').reverse()[0];
  110. changes.push({author,description,repoChangeId});
  111. });
  112. console.log("fetched details for :"+ packageName);
  113. let ownerList = [];
  114. const commitList = [];
  115. changes.forEach(function(change) {
  116. const author = change.author;
  117. const commit = author + '@: [' + change.description.split('\n')[0] + '](https://code.amazon.com/packages/'
  118. + packageName + '/commits/' + change.repoChangeId + ')';
  119. ownerList.push(author);
  120. commitList.push(commit);
  121. });
  122. ownerList = ownerList.filter((v, i, a) => a.indexOf(v) === i);
  123. unsafeWindow.packageDetails.push({
  124. "packageName": packageName,
  125. "type": "Source Changed",
  126. "diffUrl": diffUrl,
  127. "owners": ownerList,
  128. "commits": commitList
  129. });
  130. }
  131. }
  132. });
  133. }
  134.  
  135. function getPackageDiffOwners() {
  136. let allOwners = [];
  137. let diffOwnersString = "";
  138. let isPackageChecked = false;
  139. unsafeWindow.packageDetails.forEach(pkgDetail => {
  140. isPackageChecked = $("#" + pkgDetail.packageName + "Check").is(":checked");
  141. if (isPackageChecked) {
  142. diffOwnersString = diffOwnersString + "* [" + pkgDetail.packageName + "](https://code.amazon.com"
  143. + pkgDetail.diffUrl + ") [" + pkgDetail.type + "]\n";
  144. pkgDetail.owners.forEach(owner => {
  145. diffOwnersString = diffOwnersString + " * " + owner + "\n";
  146. allOwners.push(owner);
  147. })
  148. }
  149. })
  150. if (allOwners.length > 0) {
  151. allOwners = allOwners.filter((v, i, a) => a.indexOf(v) === i);
  152. diffOwnersString = diffOwnersString + "* All Owners\n";
  153. allOwners.forEach(owner => {
  154. diffOwnersString = diffOwnersString + " * " + owner + "\n";
  155. })
  156. }
  157. unsafeWindow.commitOwners=JSON.stringify(allOwners)
  158. return copyToClipboard(diffOwnersString);
  159. }
  160.  
  161. function getPackageDiffCommits() {
  162. let diffCommitsString = "";
  163. let isPackageChecked = false;
  164. unsafeWindow.packageDetails.forEach(pkgDetail => {
  165. isPackageChecked = $("#" + pkgDetail.packageName + "Check").is(":checked");
  166. if (isPackageChecked) {
  167. diffCommitsString = diffCommitsString + "* [" + pkgDetail.packageName + "](https://code.amazon.com"
  168. + pkgDetail.diffUrl + ") [" + pkgDetail.type + "]\n";
  169. pkgDetail.commits.forEach(commit => {
  170. diffCommitsString = diffCommitsString + " * " + commit + "\n";
  171. })
  172. }
  173.  
  174. })
  175.  
  176. unsafeWindow.commitChanges = diffCommitsString;
  177.  
  178. let pipelineDescription = getPipelineDescription(unsafeWindow.pipelineDetails, unsafeWindow.vsDetails)
  179. let commitDetailsDescription = getCommitDetailsDescription(diffCommitsString)
  180.  
  181. return copyToClipboard(pipelineDescription + '\n'+commitDetailsDescription);
  182. }
  183.  
  184. function copyToClipboard(text) {
  185. if (text !== "") {
  186. $("#toastDiff").text("Copied to clipboard!");
  187. $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
  188. const textarea = document.createElement("textarea");
  189. textarea.textContent = text;
  190. textarea.style.position = "fixed";
  191. document.body.appendChild(textarea);
  192. textarea.select();
  193. try {
  194. document.execCommand("copy");
  195. } catch (ex) {
  196. console.warn("Copy to clipboard failed.", ex);
  197. } finally {
  198. document.body.removeChild(textarea);
  199. return true;
  200. }
  201. } else {
  202. $("#toastDiff").text("No diff found! Try again.");
  203. $("#toastDiff").fadeIn("slow", function() { setTimeout(function() { $("#toastDiff").fadeOut("slow") }, 1500) });
  204. return false;
  205. }
  206. }
  207.  
  208. unsafeWindow.packageDetails = [];
  209. unsafeWindow.pipelineDetails = {pipelineName:'',packages:[]};
  210.  
  211. $('#packages tr.added-pkg').each(function(i, el) {
  212. const $tds = $(this).find("td");
  213. const packageUrl = $tds.eq(0).find("a").attr("href");
  214. const packageName = $tds.eq(0).text();
  215. const checkbox = document.createElement("input");
  216. checkbox.setAttribute("type", "checkbox");
  217. checkbox.setAttribute("id", (packageName + "Check"));
  218. checkbox.setAttribute("style", "margin-right:5px");
  219. $tds.eq(0).prepend(checkbox);
  220. addNewPackages(packageName, packageUrl);
  221. })
  222.  
  223. $('#packages tr.error').each(function(i, el) {
  224. const $tds = $(this).find('td');
  225. const packageUrl = $tds.eq(0).find('a').attr('href');
  226. const packageName = $tds.eq(0).text();
  227. const checkbox = document.createElement("input");
  228. checkbox.setAttribute("type", "checkbox");
  229. checkbox.setAttribute("id", (packageName + "Check"));
  230. checkbox.setAttribute("style", "margin-right:5px");
  231. $tds.eq(0).prepend(checkbox);
  232. addRemovedPackages(packageName, packageUrl);
  233. })
  234.  
  235. $('#packages tr.branch-change').each(function(i, el) {
  236. const $tds = $(this).find('td');
  237. const diffUrl = $tds.eq(5).find('a').attr('href').trim();
  238. const startHash = $tds.eq(1).find('a').attr('href').split('/')[4].trim();
  239. const endHash = $tds.eq(3).find('a').attr('href').split('/')[4].trim();
  240. const packageName = $tds.eq(0).text().trim();
  241. const oldBranchName = $tds.eq(6).text().split("->")[0].trim();
  242. const branchName = $tds.eq(6).text().split("->")[1].trim();
  243. const checkbox = document.createElement("input");
  244. checkbox.setAttribute("type", "checkbox");
  245. checkbox.setAttribute("id", (packageName + "Check"));
  246. checkbox.setAttribute("style", "margin-right:5px");
  247. $tds.eq(0).prepend(checkbox);
  248.  
  249. $(checkbox).change(function(){
  250. if(this.checked){
  251. addBranchChangePackages(packageName, branchName, oldBranchName, startHash, endHash, diffUrl);
  252. }
  253.  
  254. })
  255.  
  256.  
  257. })
  258.  
  259. $('#packages tr.source-change').each(function(i, el) {
  260. const $tds = $(this).find('td');
  261. const diffUrl = $tds.eq(5).find('a').attr('href').trim();
  262. const startHash = $tds.eq(1).find('a').attr('href').split('/')[4].trim();
  263. const endHash = $tds.eq(3).find('a').attr('href').split('/')[4].trim();
  264. const packageName = $tds.eq(0).text().trim();
  265. const branchName = $tds.eq(6).text().trim();
  266. const checkbox = document.createElement("input");
  267. checkbox.setAttribute("type", "checkbox");
  268. checkbox.setAttribute("id", (packageName + "Check"));
  269. checkbox.setAttribute("style", "margin-right:5px");
  270. $tds.eq(0).prepend(checkbox);
  271. $(checkbox).change(function(){
  272.  
  273. if(this.checked){
  274. addModifiedPackages(packageName, branchName, startHash, endHash, diffUrl);
  275. }
  276.  
  277.  
  278. })
  279.  
  280. })
  281.  
  282.  
  283. function createMCMWithTemplate(mcm_id){
  284. let diffcommit = getPackageDiffCommits();
  285. let diffpackage = getPackageDiffOwners();
  286.  
  287. window.location.href = "https://mcm.amazon.com/cms/new?from_template="+mcm_id+"&commitChanges="+encodeURIComponent(unsafeWindow.commitChanges)+"&commitOwners="+unsafeWindow.commitOwners+
  288. "&vsDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.vsDetails)) + "&pipelineDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.pipelineDetails));
  289. }
  290.  
  291. function updateMCMWithExistingDraft(mcm_id){
  292. let diffcommit = getPackageDiffCommits();
  293. let diffpackage = getPackageDiffOwners();
  294.  
  295. window.location.href = "https://mcm.amazon.com/cms/"+mcm_id+"/edit?commitChanges="+encodeURIComponent(unsafeWindow.commitChanges)+"&commitOwners="+unsafeWindow.commitOwners+
  296. "&vsDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.vsDetails)) + "&pipelineDetails=" + encodeURIComponent(JSON.stringify(unsafeWindow.pipelineDetails));
  297. }
  298.  
  299. const vsRangeDiff = document.getElementById('vs_range_diff');
  300. if (vsRangeDiff) {
  301. const mainDiv = document.createElement('div');
  302. 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>';
  303. vsRangeDiff.appendChild(mainDiv);
  304.  
  305. unsafeWindow.pipelineDetails = JSON.parse(new URLSearchParams(window.location.search).get('pipelineDetails'));
  306. unsafeWindow.vsDetails = JSON.parse(new URLSearchParams(window.location.search).get('vsDetails'));
  307.  
  308. //mark all the packages autobuilt in the pipeline as checked for diff creation
  309. window.addEventListener("load", ()=>{
  310. unsafeWindow.pipelineDetails.packages.forEach((pkg)=>{
  311. let pkgElements = document.querySelectorAll('#'+pkg+'Check');
  312. pkgElements.forEach((pkgElem)=>{
  313. $(pkgElem).click();
  314. })
  315. });
  316.  
  317. $('#createMCM').text('Create MCM');
  318. $('#createMCM').prop('disabled',false);
  319.  
  320. });
  321.  
  322.  
  323. // adding click listener for diff commits
  324. $( "#diffCommits" ).click(function() {
  325. getPackageDiffCommits();
  326. });
  327.  
  328. $( "#diffOwners" ).click(function() {
  329. getPackageDiffOwners();
  330. });
  331.  
  332. $( "#createMCM" ).click(function() {
  333. const mcm_id = $('#mcm_id').val();
  334. if(mcm_id){
  335. updateMCMWithExistingDraft(mcm_id);
  336. }
  337. else {
  338. createMCMWithTemplate('7e9a85a5-bd0a-4560-8379-00a73ef82f1b');
  339. }
  340. });
  341.  
  342.  
  343. }
  344.  
  345.  
  346. //========== This part is run in pipelines.amazon.com ==========
  347. function getVSDiffLink(href) {
  348. const splittedHref = href.split("&");
  349. const currVS = splittedHref[0].split("=")[1];
  350. const currRev = splittedHref[1].split("=")[1];
  351. const prevVS = splittedHref[3].split("=")[1];
  352. const prevRev = splittedHref[4].split("=")[1];
  353. const vsDetails = {};
  354. vsDetails.cur = currRev;
  355. vsDetails.prev = prevRev;
  356. vsDetails.name = prevVS;
  357.  
  358.  
  359. return "https://code.amazon.com/version-sets/" + currVS + "/revisions/" + currRev + "?previous=" + prevRev + "&previous_vs=" + prevVS +
  360. "&pipelineDetails=" + sessionStorage.getItem('pipelineDetails') +
  361. "&vsDetails=" + JSON.stringify(vsDetails);
  362. }
  363.  
  364. const pipelineName = document.querySelector('.pipeline-name-header');
  365.  
  366. if(pipelineName){
  367. unsafeWindow.pipelineDetails.pipelineName = pipelineName.innerText;
  368. }
  369.  
  370.  
  371.  
  372. const packages = document.querySelector('#stage-name-Packages > div > h3 > span');
  373. if(packages){
  374. document.querySelectorAll('.autobuild .target-details > div.name').forEach((pkg)=>{
  375. let pkg_name = pkg.getAttribute('data-targetname').split('/')[0]
  376. unsafeWindow.pipelineDetails.packages.push(pkg_name)
  377.  
  378. })
  379.  
  380. sessionStorage.setItem("pipelineDetails",JSON.stringify(unsafeWindow.pipelineDetails))
  381.  
  382. }
  383.  
  384. const diffLinkElements = document.getElementsByClassName("diff-link");
  385. if (diffLinkElements && !packages) {
  386. for(let i=0; i < diffLinkElements.length; i++) {
  387. const element = diffLinkElements[i];
  388. const href = element.getElementsByTagName("a")[0].getAttribute("href");
  389. const vsDiffLink = getVSDiffLink(href);
  390. const aTag = document.createElement("a");
  391. aTag.setAttribute("href", vsDiffLink);
  392. aTag.innerText = "VS";
  393. element.appendChild(aTag);
  394. }
  395. }
  396.  
  397. //this code part will run in mcm
  398.  
  399. const insideMcm = $('.mcm-logo').length
  400.  
  401.  
  402. function getPipelineDescription(pipelineDetails, vsDetails){
  403.  
  404. let pipelineDescription = ""
  405.  
  406. pipelineDescription+= `## ${pipelineDetails.pipelineName}\n`
  407. pipelineDescription+= `- Pipeline Link - https://pipelines.amazon.com/pipelines/${pipelineDetails.pipelineName}\n`
  408. 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`
  409. pipelineDescription+= `- Current VFI - ${vsDetails.name}@B${vsDetails.prev}\n`
  410. pipelineDescription+= `- VFI to be Deployed - ${vsDetails.name}@B${vsDetails.cur}\n`
  411.  
  412. return pipelineDescription;
  413.  
  414.  
  415.  
  416.  
  417.  
  418. }
  419.  
  420. function getCommitDetailsDescription(commitDetails){
  421.  
  422. let commitChanges = "";
  423. if(commitDetails){
  424. commitChanges+="#### Changes to be promoted in prod:\n"
  425. commitChanges+=commitDetails;
  426. }
  427.  
  428. return commitChanges;
  429. }
  430.  
  431. function updateDescription(commitDetails, vsDetails, pipelineDetails){
  432. const curr = $('#description').val()
  433. const rollbackId = "## Rollback Procedure";
  434.  
  435. const pipelineDescription=getPipelineDescription(pipelineDetails, vsDetails);
  436. console.log(pipelineDescription);
  437.  
  438. 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?');
  439.  
  440. let commitChanges = getCommitDetailsDescription(commitDetails);
  441.  
  442. let data = curr.substr(0, pos) + pipelineDescription+'\n'+ commitChanges+'\n' + curr.substr(pos);
  443.  
  444. $('#description').val(data)
  445.  
  446. }
  447.  
  448. function updateMCMTitle(pipelineDetails){
  449. const title = $('#title')
  450. title.val(title.val()+pipelineDetails.pipelineName)
  451.  
  452. }
  453.  
  454. function addcommitOwners(commitOwners) {
  455.  
  456. commitOwners.forEach((owner)=>{
  457. $('#add_approver_login').val(owner)
  458. $('#add_approver').click()
  459. })
  460.  
  461. }
  462.  
  463. if(insideMcm>0){
  464. const data = new URLSearchParams(window.location.search);
  465. const commitDetails = decodeURIComponent(data.get('commitChanges'));
  466. const versionSetDetails = JSON.parse(decodeURIComponent(data.get('vsDetails')));
  467. const pipelineDetails = JSON.parse(decodeURIComponent(data.get('pipelineDetails')));
  468. console.log(pipelineDetails);
  469. console.log(versionSetDetails);
  470. console.log(commitDetails);
  471. const commitOwners = JSON.parse(data.get('commitOwners'));
  472. updateMCMTitle(pipelineDetails);
  473.  
  474. if(versionSetDetails){
  475. updateDescription(commitDetails, versionSetDetails, pipelineDetails)
  476. }
  477. if(commitOwners && commitOwners.length>0){
  478. addcommitOwners(commitOwners);
  479. }
  480. }
  481.  
  482. })();