Skip to content

Instantly share code, notes, and snippets.

@sylwit
Last active January 23, 2025 16:35
Show Gist options
  • Save sylwit/eece2580b1872acb9274b43f43a3a166 to your computer and use it in GitHub Desktop.
Save sylwit/eece2580b1872acb9274b43f43a3a166 to your computer and use it in GitHub Desktop.
CircleCI sum durations
// ==UserScript==
// @name CircleCI sum durations
// @namespace https://sylwit.com/
// @match https://app.circleci.com/pipelines/*
// @grant none
// @version 1.1
// @author @sylwit
// @description Display the sum of durations across all workflows in a pipeline
// ==/UserScript==
(function() {
'use strict';
const documentObserver = new MutationObserver(() => {
const targetNode = document.querySelector('div[data-cy="workflow-graph"]');
if (targetNode) {
console.log('Element found with the data "workflow-graph".');
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 timeSpans = document.querySelectorAll('div[data-cy="workflow-graph"] a[data-cy="job-node"] > div:last-child > span');
const totalSeconds = Array.from(timeSpans).reduce((total, element) => {
element.style.fontWeight = 'bold';
const value = element.textContent.trim();
return total + getTotalSeconds(value);
}, 0);
if (totalSeconds == 0) return;
const totalDurationDiv = document.querySelector('svg[aria-label="Duration"]').closest('p').closest('div');
if (!totalDurationDiv) {
console.error('Total duration div not found.');
return;
}
// console.log(totalDurationDiv)
// we clone the total duration block to keep the style
const newDiv = totalDurationDiv.cloneNode(true);
newDiv.querySelector('p > div').textContent = 'Sum durations'
newDiv.querySelector('p > span > div').textContent = formatDurationFromSeconds(totalSeconds)
totalDurationDiv.insertAdjacentElement('afterend', 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