Skip to content

Instantly share code, notes, and snippets.

@ChrisMBarr
Last active May 2, 2018 18:30
Show Gist options
  • Save ChrisMBarr/d7ee9666f5d75f3cbecd63a6c347efe5 to your computer and use it in GitHub Desktop.
Save ChrisMBarr/d7ee9666f5d75f3cbecd63a6c347efe5 to your computer and use it in GitHub Desktop.
Show a visual representation of how long each TFS build task took
.buildvnext-build-details-header .summary .reason,
.buildvnext-build-details-header .summary .duration{
width: 60%;
}
.buildvnext-build-details-header .summary {
padding-right: 106px;
}
#timeline-bar-chart {
height: 42px;
border: 1px solid #c7e0f4;
display: flex;
float: right;
width: 40%;
}
#timeline-bar-chart .bar {
background: #71afe5;
flex-grow: 1;
min-width: 5px;
}
#timeline-bar-chart .bar:nth-child(even){
background: #c7e0f4;
}
#timeline-bar-chart .bar:hover {
opacity: .8;
}
<script>
function getDurationInMills(step) {
const start = new Date(step.startTime);
const finish = new Date(step.finishTime);
return finish.getTime() - start.getTime();
}
function millsToFriendlyTime(mills) {
const totalSeconds = (mills / 1000);
const divisor_for_minutes = totalSeconds % (60 * 60)
const minutes = Math.floor(divisor_for_minutes / 60);
let seconds = Math.floor(divisor_for_minutes % 60);
if (seconds < 10) {
seconds = '0' + seconds;
}
return `${minutes}:${seconds}`;
}
function getParsedSteps(buildSteps) {
const orderedBuildSteps = buildSteps.sort((a, b) => {
if(a.order < b.order) return -1;
if(a.order > b.order) return 1;
return 0;
});
let totalDuration = 0;
let parsedSteps = [];
for (const step of orderedBuildSteps) {
if(step.type === 'Task') {
const duration = getDurationInMills(step);
totalDuration += duration;
parsedSteps.push({
name: step.name,
duration: duration,
friendlyDuration: millsToFriendlyTime(duration),
percent: 0
});
}
}
//Now we can calculate the percentages for each step
parsedSteps = parsedSteps.map(s => {
s.percent = parseFloat(((s.duration / totalDuration) * 100).toFixed(2));
return s;
})
return parsedSteps;
}
function getChartHtml(steps) {
let html = '<div id="timeline-bar-chart">'
for (const item of steps) {
html += `<div class="bar" style="width: ${item.percent}%;" title="${item.name} - ${item.friendlyDuration} (${item.percent}%)"></div>`;
}
html += '</div>'
return html;
}
function init($el) {
const baseUrl = __vssPageContext.webContext.collection.uri;
const projectId = __vssPageContext.webContext.project.id;
const buildIdPattern = location.search.match(/buildId=(\d+)/);
if (buildIdPattern && buildIdPattern[1]) {
const buildId = buildIdPattern[1];
$.getJSON(`${baseUrl}/${projectId}/_apis/build/builds/${buildId}`)
.then(build => {
//We can't show a graph if the build is not done yet.
if (build.status === 'completed') {
$.getJSON(`${baseUrl}/${projectId}/_apis/build/builds/${buildId}/Timeline`)
.then(d => {
const steps = getParsedSteps(d.records);
const chartHtml = getChartHtml(steps);
$el.prepend(chartHtml);
});
}
});
}
}
//This runs quickly, so we need to make sure we have jQuery and the related elements available before we can do anything
let initAttempts = 0 ;
let elementCheckerTimer = setInterval(() => {
if(typeof $ === 'function') {
const $el = $('#buildvnext-view-result .summary')
if($el.length > 0 && $el.text() !== '') {
init($el);
clearInterval(elementCheckerTimer);
}
}
//Make sure it doesn't run forever if it fails after a while
initAttempts++;
if(initAttempts > 30) {
clearInterval(elementCheckerTimer);
}
}, 250);
</script>
^https://tfs.example-company.com/tfs/ExampleCollection/.+/_build/index
@ChrisMBarr
Copy link
Author

What Is This?

Use this if you use TFS, when you view a build this will get the duration of each build task and show them visually as a stacked bar chart so you can get an idea of how long each task is taking.

How to Install

Step 1: Use Chrome & Install the Personalized Web Options extention.

Step 2 Go into the extension options and create a new rule.

Step 3 Add the text above into the corresponding "Match URL" and "Add HTML" fields.
(NOTE: The script that does the magic here in enclosed in <script> tags and must be placed in the HTML field, and not into the script field of the extension! This allows it to have access to the HTML nodes that it needs.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment