Skip to content

Instantly share code, notes, and snippets.

@sylwit
Last active August 3, 2024 20:15
Show Gist options
  • Save sylwit/9cfaed41d041ed3cd06056d6d79f1058 to your computer and use it in GitHub Desktop.
Save sylwit/9cfaed41d041ed3cd06056d6d79f1058 to your computer and use it in GitHub Desktop.
Github Actions sum durations - userscript
// ==UserScript==
// @name Github Actions sum durations
// @namespace https://sylwit.com/
// @match https://github.com/*
// @grant none
// @version 1.0
// @author @sylwit
// @description Display the sum of durations across all jobs in a workflow
// ==/UserScript==
(function() {
'use strict';
function initializeObserver(targetNode) {
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
updateTimeSpent();
}
}
});
// Start observing the target node for configured mutations
observer.observe(targetNode, {
childList: true,
subtree: true
});
}
const documentObserver = new MutationObserver(() => {
const targetNode = document.querySelector('.visual-graph');
if (targetNode) {
// console.log('Element found with the class "visual-graph".');
initializeObserver(targetNode); // Initialize the observer for the target node
updateTimeSpent();
// Disconnect the document observer since we found the target node
documentObserver.disconnect();
}
});
// Start observing the document body for changes
documentObserver.observe(document.body, {
childList: true,
subtree: true
});
function updateTimeSpent() {
const timeDivs = document.querySelectorAll('streaming-graph-job div[data-view-component="true"] + div');
const totalSeconds = Array.from(timeDivs).reduce((total, element) => {
element.style.fontWeight = 'bold';
const value = element.textContent.trim();
return total + getTotalSeconds(value);
}, 0);
if (totalSeconds == 0) return;
const summaryDiv = document.querySelector('div[aria-label="Workflow run summary"] > div');
const totalDurationDiv = summaryDiv.children[3]
if (!totalDurationDiv) {
console.error('Total duration div not found.');
return;
}
// we clone the total duration block to keep the style
const newDiv = totalDurationDiv.cloneNode(true);
newDiv.querySelector('span').textContent = 'Sum durations'
newDiv.querySelector('a').textContent = formatDurationFromSeconds(totalSeconds)
summaryDiv.appendChild(newDiv);
}
function getTotalSeconds(timeString) {
// Use a regular expression to match hours, minutes, and seconds
const regex = /(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?/;
const match = timeString.match(regex);
if (!match) {
throw new Error('Invalid time format');
}
// Convert matched strings to numbers, defaulting to 0 if not present
const hours = parseInt(match[1] || '0', 10) * 3600;
const minutes = parseInt(match[2] || '0', 10) * 60;
const seconds = parseInt(match[3] || '0', 10);
return (hours + minutes + seconds)
}
function formatDurationFromSeconds(duration) {
const hours = Math.floor(duration / 3600)
const minutes = Math.floor(duration % 3600 / 60)
const seconds = duration % 60
return `${hours}h ${minutes}m ${seconds}s`
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment