Skip to content

Instantly share code, notes, and snippets.

@DeflateAwning
Last active April 2, 2024 18:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DeflateAwning/d8d42a082cb27b7d01df751d0dc26f31 to your computer and use it in GitHub Desktop.
Save DeflateAwning/d8d42a082cb27b7d01df751d0dc26f31 to your computer and use it in GitHub Desktop.
Tampermonkey script to modify the AWS Batch run page to add an execution time field to the "Job attempts" tab
// ==UserScript==
// @name AWS Batch Upgrades
// @namespace https://gist.github.com/DeflateAwning/d8d42a082cb27b7d01df751d0dc26f31
// @version 0.2.3
// @description Calculate AWS Batch job time since started and total execution time (on the "Job attempts" tab). Set the tab title to include the job name and execution status.
// @author DeflateAwning
// @match https://us-east-1.console.aws.amazon.com/batch/home?region=*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const PRERUN_STATUS_VALUES = ['Runnable', 'Submitted', 'Starting', 'Ready'];
function calculateTimeDifference(startedAt, stoppedAt) {
if (!startedAt || !stoppedAt) {
return "N/A";
}
const timeDifference = stoppedAt.getTime() - startedAt.getTime();
const hours = Math.floor(timeDifference / (1000 * 60 * 60));
const minutes = Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeDifference % (1000 * 60)) / 1000);
return `${hours}h ${minutes}m ${seconds}s`;
}
// Function to create and append the result field
function appendExecutionTimeField() {
// Get the "Started at" and "Stopped at" elements
const startedAtElement = document.querySelector('[data-test-id="startedAt"]').querySelector('div:last-child');
const stoppedAtElement = document.querySelector('[data-test-id="stoppedAt"]').querySelector('div:last-child');
// Extract the text content from the elements
const startedAtText = startedAtElement.textContent.trim();
const stoppedAtText = stoppedAtElement.textContent.trim();
// Create date objects
const startedAtDate = new Date(startedAtText);
const stoppedAtDate = new Date(stoppedAtText);
const timeSinceStarted = calculateTimeDifference(startedAtDate, new Date());
const totalExecutionTime = calculateTimeDifference(startedAtDate, stoppedAtDate);
// remove existing "tamper-time-summary" objects
document.querySelectorAll('.tamper-time-summary').forEach(e => e.remove());
const newElementHTML = `
<div style="color: rgb(139,0,0);" class="tamper-time-summary">
<div>
<span style="color: #545b64;">Time Since Start:</span>
${timeSinceStarted}
</div>
<div>
<span style="color: #545b64;">Total Execution Time:</span>
${totalExecutionTime}
</div>
<div style="color: #545b64;">Note: Times are in local time.</div>
</div>
`;
// Find the target element by its data-test-id
const targetElement = document.querySelector('[data-test-id="stoppedAt"]');
// append newElementHTML right after targetElement
targetElement.insertAdjacentHTML('afterend', newElementHTML);
}
function getJobName() {
// let jobNameElement = document.evaluate('//*[@id="heading:rm:"]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
let jobNameElement = document.evaluate('//*[@id="app"]/div/div/div/main/div/div[2]/div/div/nav/ol/li[4]/div/span/span', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (jobNameElement) {
let jobName = jobNameElement.textContent.trim();
return jobName;
}
}
function getJobStatus() {
let jobStatusElement = document.evaluate("//*[contains(@class, 'awsui_content-wrapper')]/div[2]/div/div/div/div/div[1]/div/div/div[2]/span/span/text()", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
if (jobStatusElement) {
let jobStatus = jobStatusElement.textContent.trim();
return jobStatus;
}
}
function checkAndUpdatePageTitle() {
var currentUrl = window.location.href;
// Check if the URL ends with "#jobs" and contains "/batch/home"
if (currentUrl.endsWith("#jobs") && currentUrl.includes("/batch/home")) {
// Set the page title
document.title = "JOB LIST | AWS Batch";
// console.log("Set job title.");
}
else if (currentUrl.includes("/batch/home") && currentUrl.includes("#jobs/fargate/detail/")) {
let job_name = getJobName();
let job_status = getJobStatus();
let new_title = '';
if (job_status == 'Running') {
new_title += '🕙';
}
else if (job_status == 'Success' || job_status == 'Succeeded') {
new_title += '✅';
}
else if (job_status == 'Failed') {
new_title += '❌';
}
else if (PRERUN_STATUS_VALUES.includes(job_status)) { // like 'Starting'
new_title += '🏁';
}
else if (job_status) {
new_title += '🤷'; // non-null but unknown
}
else {
new_title += '🫙'; // null jar
}
new_title += ' | ';
if (job_name) {
new_title += job_name;
}
else {
new_title += 'JOB';
//console.log("getJobName() returned empty job name.");
}
new_title += ' | BATCH';
document.title = new_title;
}
}
function run_ignore_error() {
try {
appendExecutionTimeField();
} catch (error) {
console.error('appendExecutionTimeField: An error occurred:', error);
}
try {
checkAndUpdatePageTitle();
} catch (error) {
console.error('checkAndUpdatePageTitle: An error occurred:', error);
}
}
// run every 5 seconds to keep time updated
setInterval(run_ignore_error, 2000);
// auto-reload the page if it says the job is Running
setInterval(function() {
let job_status = getJobStatus();
if (job_status == 'Running' || PRERUN_STATUS_VALUES.includes(job_status)) {
location.reload();
}
}, 60 * 4 * 1000); // 4 minutes
// debug run
// appendExecutionTimeField();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment