Skip to content

Commit

Permalink
(feature) Instead of the average build time, time elapsed since last …
Browse files Browse the repository at this point in the history
…build is displayed. The average build time is now only displayed when the build is in progress.

Closes #63 and closes #120.
  • Loading branch information
jan-molak committed Feb 9, 2015
1 parent 84dbf1a commit cd3d664
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;

import com.smartcodeltd.jenkinsci.plugins.buildmonitor.facade.RelativeLocation;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.HumanReadableDuration;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.DurationInMilliseconds;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.BuildAugmentor;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.bfa.Analysis;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.claim.Claim;
Expand Down Expand Up @@ -60,17 +63,22 @@ private boolean isRunning(Run<?, ?> build) {

@Override
public Duration elapsedTime() {
return new Duration(now() - whenTheBuildStarted());
return new HumanReadableDuration(now() - whenTheBuildStarted());
}

@Override
public Duration timeElapsedSince() {
return new DurationInMilliseconds(now() - (whenTheBuildStarted() + build.getDuration()));
}

@Override
public Duration duration() {
return new Duration(build.getDuration());
return new HumanReadableDuration(build.getDuration());
}

@Override
public Duration estimatedDuration() {
return new Duration(build.getEstimatedDuration());
return new HumanReadableDuration(build.getEstimatedDuration());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;

import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration;
import hudson.model.Result;

import java.util.List;
Expand All @@ -12,6 +13,7 @@ public interface BuildViewModel {

public boolean isRunning();
public Duration elapsedTime();
public Duration timeElapsedSince();
public Duration duration();
public Duration estimatedDuration();
public int progress();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;

import com.smartcodeltd.jenkinsci.plugins.buildmonitor.facade.RelativeLocation;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration;
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.plugins.BuildAugmentor;
import hudson.model.Job;
import hudson.model.Result;
Expand Down Expand Up @@ -96,6 +97,11 @@ public String estimatedDuration() {
return formatted(lastBuild().estimatedDuration());
}

@JsonProperty
public String timeElapsedSinceLastBuild() {
return formatted(lastCompletedBuild().timeElapsedSince());
}

private String formatted(Duration duration) {
return null != duration
? duration.toString()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;

import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration;
import hudson.model.Result;

import java.util.HashSet;
Expand Down Expand Up @@ -33,6 +34,11 @@ public Duration elapsedTime() {
return null;
}

@Override
public Duration timeElapsedSince() {
return null;
}

@Override
public Duration duration() {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration;

abstract public class Duration {

protected final long duration;

public Duration(long milliseconds) {
this.duration = milliseconds;
}

abstract public String toString();

public boolean greaterThan(Duration otherDuration) {
return duration > otherDuration.toLong();
}

private long toLong() {
return duration;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration;

public class DurationInMilliseconds extends Duration {

public DurationInMilliseconds(long milliseconds) {
super(milliseconds);
}

@Override
public String toString() {
return String.valueOf(duration);
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel;

class Duration {//todo: extract the Duration class, or move it to a BuildView class

private final long duration;
package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration;

// todo: is this still needed? backend could pass the duration in milliseconds to the frontend, which in turn could make it human-readable
public class HumanReadableDuration extends Duration {
private final static long MILLISECOND = 1;
private final static long SECONDS = 1000 * MILLISECOND;
private final static long MINUTES = 60 * SECONDS;
private final static long HOURS = 60 * MINUTES;

public Duration(long milliseconds) {
this.duration = milliseconds;
public HumanReadableDuration(long milliseconds) {
super(milliseconds);
}

public long hours() {
private long hours() {
return duration / HOURS;
}

public long minutes() {
private long minutes() {
return (duration - (hours() * HOURS)) / MINUTES;
}

public long seconds() {
private long seconds() {
return (duration - (hours() * HOURS) - (minutes() * MINUTES)) / SECONDS;
}

public boolean greaterThan(Duration otherDuration) {
return duration > otherDuration.toLong();
}

@Override
public String toString() {
String formatted;

Expand All @@ -38,9 +33,4 @@ public String toString() {

return formatted;
}


private long toLong() {
return duration;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@

<script src="${resourcesURL}/scripts/app.js"></script>
<script src="${resourcesURL}/scripts/cron.js"></script>
<script src="${resourcesURL}/scripts/filters.js"></script>
<script src="${resourcesURL}/scripts/services.js"></script>
<script src="${resourcesURL}/scripts/jenkins.js"></script>
<script src="${resourcesURL}/scripts/controllers.js"></script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
title="Details of {{job.name}}, build {{job.lastBuildName}}"
href="{{job.lastBuildUrl}}">{{job.lastBuildName}}</a>

<div data-ng-show="!!job.estimatedDuration" class="build-time">
<span class="elapsed" data-ng-show="job.progress > 0">{{job.lastBuildDuration}}</span>
<div data-ng-show="!!job.estimatedDuration &amp;&amp; job.progress > 0" class="build-time">
<span class="elapsed">{{job.lastBuildDuration}}</span>
<span class="estimated">{{job.estimatedDuration}}</span>
</div>

<div data-ng-show="!!job.timeElapsedSinceLastBuild &amp;&amp; job.progress == 0" class="build-time">
<span class="estimated">{{job.timeElapsedSinceLastBuild | estimatedTimeElapsedSince}}</span>
</div>
</div>
</li>
</ul>
7 changes: 6 additions & 1 deletion src/main/webapp/scripts/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
'use strict';

angular.
module('buildMonitor', [ 'buildMonitor.controllers', 'buildMonitor.settings', 'slugifier' ]).
module('buildMonitor', [
'buildMonitor.controllers',
'buildMonitor.filters',
'buildMonitor.settings',
'slugifier'
]).
run(['$rootScope', 'notifyUser', function($rootScope, notifyUser) {
$rootScope.settings = { };

Expand Down
42 changes: 42 additions & 0 deletions src/main/webapp/scripts/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use strict';

angular.
module('buildMonitor.filters', []).

filter('estimatedTimeElapsedSince', function() {
var seconds = 1000,
minutes = 60 * seconds,
hours = 60 * minutes,
days = 24 * hours,
months = 30 * days,
years = 12 * months,

unitsOfTime = [
{ divisor: seconds, singular: "%d second ago", plural: "%d seconds ago" },
{ divisor: minutes / seconds, singular: "%d minute ago", plural: "%d minutes ago" },
{ divisor: hours / minutes, singular: "%d hour ago", plural: "%d hours ago" },
{ divisor: days / hours, singular: "%d day ago", plural: "%d days ago" },
{ divisor: months / days, singular: "%d month ago", plural: "%d months ago" },
{ divisor: years / months, singular: "over a year ago", plural: "hasn't run in ages!" }
];

function humanFriendly(remainder, unitOfTime) {
var rounded = Math.round(remainder);

return (rounded === 1 ? unitOfTime.singular : unitOfTime.plural).replace("%d", rounded);
}

function approximate(remainder, unitOfTime, tail) {
return (tail.length === 0 || remainder < tail[0].divisor) ?
humanFriendly(remainder, unitOfTime) :
approximate(remainder / tail[0].divisor, tail[0], tail.slice(1));
}

return function(ago) {
switch(true) {
case (ago <= 30 * seconds): return "just now :-)";
case (ago <= 5 * minutes): return "a moment ago";
default: return approximate(ago / unitsOfTime[0].divisor, unitsOfTime[0], unitsOfTime.slice(1));
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.JobStateRecipe;
import hudson.model.Job;
import hudson.model.Result;
import org.junit.Ignore;
import org.junit.Test;

import java.text.ParseException;
Expand Down Expand Up @@ -40,7 +39,6 @@ public class JobViewTest {

private RelativeLocation relative = mock(RelativeLocation.class);
private JobView view;

/*
* By the way, if you were not aware of this: the configuration page of each job has an "Advanced Project Options"
* section, where you can set a user-friendly "Display Name"
Expand Down Expand Up @@ -194,7 +192,21 @@ public void should_not_say_anything_if_it_doesnt_know_how_long_the_next_build_is

assertThat(view.estimatedDuration(), is(""));
}


/*
* Last build, last success and last failure (ISO 8601)
*/
@Test
public void should_know_how_long_since_the_last_build_happened() throws Exception {
String tenMinutesInMilliseconds = String.format("%d", 10 * 60 * 1000);

view = JobView.of(a(job().whereTheLast(build().startedAt("18:05:00").andTook(5))),
assumingThatCurrentTimeIs("18:20:00")
);

assertThat(view.timeElapsedSinceLastBuild(), is(tenMinutesInMilliseconds));
}

/*
* Should produce a meaningful status description that can be used in the CSS
*/
Expand Down
57 changes: 57 additions & 0 deletions src/test/javascript/unit/filters/estimatedTimeElapsedSinceSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

describe('buildMonitor', function () {
describe('buildMonitor.filters', function () {
describe('estimatedTimeElapsedSince', function () {
var milliseconds = 1,
seconds = 1000 * milliseconds,
minutes = 60 * seconds,
hours = 60 * minutes,
days = 24 * hours,
months = 30 * days,
years = 12 * months;

beforeEach(module('buildMonitor.filters'));

it('converts time in milliseconds to something more user-friendly (under 5 minutes)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter(0 * minutes)).toEqual("just now :-)");
expect(estimatedTimeElapsedSinceFilter(0.5 * minutes)).toEqual("just now :-)");
expect(estimatedTimeElapsedSinceFilter(3 * minutes)).toEqual("a moment ago");
expect(estimatedTimeElapsedSinceFilter(4.5 * minutes)).toEqual("a moment ago");
}));

it('approximates to the nearest minute (under 1 hour)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter( 5.5 * minutes)).toEqual("6 minutes ago");
expect(estimatedTimeElapsedSinceFilter(10.75 * minutes)).toEqual("11 minutes ago");
expect(estimatedTimeElapsedSinceFilter(30 * minutes)).toEqual("30 minutes ago");
expect(estimatedTimeElapsedSinceFilter(59 * minutes)).toEqual("59 minutes ago");
}));

it('approximates to the nearest hour (under 1 day)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter(61 * minutes)).toEqual("1 hour ago");
expect(estimatedTimeElapsedSinceFilter(1.3 * hours )).toEqual("1 hour ago");
expect(estimatedTimeElapsedSinceFilter(1.5 * hours )).toEqual("2 hours ago");
expect(estimatedTimeElapsedSinceFilter( 2 * hours )).toEqual("2 hours ago");
expect(estimatedTimeElapsedSinceFilter(17.61 * hours )).toEqual("18 hours ago");
}));

it('approximates to the nearest day (under 1 month)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter( 1.25 * days)).toEqual("1 day ago");
expect(estimatedTimeElapsedSinceFilter( 2 * days)).toEqual("2 days ago");
expect(estimatedTimeElapsedSinceFilter( 7 * days)).toEqual("7 days ago");
}));

it('approximates to the nearest month (under 1 year)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter(31 * days )).toEqual("1 month ago");
expect(estimatedTimeElapsedSinceFilter( 2.3 * months)).toEqual("2 months ago");
expect(estimatedTimeElapsedSinceFilter(11.7 * months)).toEqual("12 months ago");
}));

it('approximates to the nearest year (boy, I really hope no one needs this range!)', inject(function (estimatedTimeElapsedSinceFilter) {
expect(estimatedTimeElapsedSinceFilter(13 * months)).toEqual("over a year ago");
expect(estimatedTimeElapsedSinceFilter(25 * months)).toEqual("hasn't run in ages!");
expect(estimatedTimeElapsedSinceFilter(5 * years)).toEqual("hasn't run in ages!");
}));
});
});
});

1 comment on commit cd3d664

@Brantone
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the quirky sayins in the Javascript, just curious why all the math on the JS side as opposed to it being passed from the backend using built-in Jenkins functions ... my own version/change using Util.getPastTimeString:
Brantone@11420f1

@jan-molak Curious on ^^ ... all that JS seems bit over-kill if Jenkins has the "time since last" already available.

Please sign in to comment.