This commit is contained in:
oleg 2015-07-26 20:00:06 +03:00
commit 4366105cf8
12 changed files with 207 additions and 46 deletions

View File

@ -3,6 +3,7 @@
var Steppy = require('twostep').Steppy, var Steppy = require('twostep').Steppy,
_ = require('underscore'), _ = require('underscore'),
Node = require('./node').Node, Node = require('./node').Node,
getAvgProjectBuildDuration = require('./project').getAvgProjectBuildDuration,
EventEmitter = require('events').EventEmitter, EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits, inherits = require('util').inherits,
notifier = require('./notifier'), notifier = require('./notifier'),
@ -226,8 +227,10 @@ Distributor.prototype.run = function(params, callback) {
} else { } else {
this.pass(null); this.pass(null);
} }
getAvgProjectBuildDuration(params.projectName, this.slot());
}, },
function(err, hasScmChanges) { function(err, hasScmChanges, avgProjectBuildDuration) {
if (params.withScmChangesOnly && !hasScmChanges) { if (params.withScmChangesOnly && !hasScmChanges) {
logger.log( logger.log(
'Building of "%s" skipped coz no scm changes', 'Building of "%s" skipped coz no scm changes',
@ -236,6 +239,8 @@ Distributor.prototype.run = function(params, callback) {
return callback(); return callback();
} }
project.avgBuildDuration = avgProjectBuildDuration;
self._updateBuild({}, { self._updateBuild({}, {
project: project, project: project,
initiator: params.initiator, initiator: params.initiator,

View File

@ -5,6 +5,7 @@ var Steppy = require('twostep').Steppy,
path = require('path'), path = require('path'),
_ = require('underscore'), _ = require('underscore'),
reader = require('./reader'), reader = require('./reader'),
db = require('../db'),
utils = require('./utils'); utils = require('./utils');
@ -99,3 +100,27 @@ exports.create = function(baseDir, config, callback) {
callback callback
); );
}; };
exports.getAvgProjectBuildDuration = function(projectName, callback) {
Steppy(
function() {
// get last done builds to calc avg build time
db.builds.find({
start: {
projectName: projectName,
status: 'done',
descCreateDate: ''
},
limit: 10
}, this.slot());
},
function(err, doneBuilds) {
var durationsSum = _(doneBuilds).reduce(function(memo, build) {
return memo + (build.endDate - build.startDate);
}, 0);
this.pass(Math.round(durationsSum / doneBuilds.length));
},
callback
);
};

View File

@ -30,7 +30,9 @@ module.exports = function(app) {
_(builds).each(function(build) { _(builds).each(function(build) {
delete build.stepTimings; delete build.stepTimings;
delete build.scm.changes; delete build.scm.changes;
build.project = _(build.project).pick('name', 'scm'); build.project = _(build.project).pick(
'name', 'scm', 'avgBuildDuration'
);
}); });
res.send(builds); res.send(builds);

View File

@ -2,6 +2,8 @@
var Steppy = require('twostep').Steppy, var Steppy = require('twostep').Steppy,
_ = require('underscore'), _ = require('underscore'),
getAvgProjectBuildDuration =
require('../lib/project').getAvgProjectBuildDuration,
createBuildDataResource = require('../distributor').createBuildDataResource, createBuildDataResource = require('../distributor').createBuildDataResource,
logger = require('../lib/logger')('projects resource'), logger = require('../lib/logger')('projects resource'),
db = require('../db'); db = require('../db');
@ -26,14 +28,16 @@ module.exports = function(app) {
function() { function() {
project = _(app.projects).findWhere(req.data); project = _(app.projects).findWhere(req.data);
// get last done builds to calc avg build time getAvgProjectBuildDuration(project.name, this.slot());
// get last done build
db.builds.find({ db.builds.find({
start: { start: {
projectName: project.name, projectName: project.name,
status: 'done', status: 'done',
descCreateDate: '' descCreateDate: ''
}, },
limit: 10 limit: 1
}, this.slot()); }, this.slot());
// tricky but effective streak counting inside filter goes below // tricky but effective streak counting inside filter goes below
@ -60,17 +64,9 @@ module.exports = function(app) {
doneBuildsStreakCallback(err, doneBuildsStreak); doneBuildsStreakCallback(err, doneBuildsStreak);
}); });
}, },
function(err, doneBuilds, doneBuildsStreak) { function(err, avgProjectBuildDuration, lastDoneBuilds, doneBuildsStreak) {
project.lastDoneBuild = doneBuilds[0]; project.lastDoneBuild = lastDoneBuilds[0];
project.avgBuildDuration = avgProjectBuildDuration;
var durationsSum = _(doneBuilds).reduce(function(memo, build) {
return memo + (build.endDate - build.startDate);
}, 0);
project.avgBuildDuration = Math.round(
durationsSum / doneBuilds.length
);
project.doneBuildsStreak = doneBuildsStreak; project.doneBuildsStreak = doneBuildsStreak;
res.send(project); res.send(project);

View File

@ -14,15 +14,12 @@
.row(); .row();
padding: 15px 0; padding: 15px 0;
margin-bottom: 3px; margin-bottom: 3px;
background: lighten(@well-bg, 3%);
&__in-progress { &_status {
background: lighten(@brand-info, 40%); float: left;
} padding-top: 14px;
&__done { margin-right: 13px;
background: lighten(@brand-success, 50%);
}
&__error {
background: lighten(@brand-danger, 30%);
} }
&_info { &_info {
@ -34,8 +31,21 @@
.make-md-column(3); .make-md-column(3);
.make-xs-column(3); .make-xs-column(3);
.text-right; .text-right;
margin-top: 8px;
&_buttons {
margin-top: 8px;
transition: opacity 0.2s ease;
opacity: 0;
}
&_progress {
padding-top: 14px;
.progress {
margin-bottom: 0;
}
}
} }
&_content { &_content {
.make-md-column(9); .make-md-column(9);
.make-xs-column(9); .make-xs-column(9);
@ -48,4 +58,72 @@
font-size: inherit; font-size: inherit;
} }
} }
&:hover {
.build_controls_buttons {
opacity: 1;
}
}
} }
@animation-duration: 1.5s;
.status {
width: 25px;
height: 25px;
border-radius: 50%;
&__in-progress {
background: @brand-info;
-webkit-animation: pulsate @animation-duration ease-out;
-webkit-animation-iteration-count: infinite;
-moz-animation: pulsate @animation-duration ease-out;
-moz-animation-iteration-count: infinite;
-o-animation: pulsate @animation-duration ease-out;
-o-animation-iteration-count: infinite;
animation: pulsate @animation-duration ease-out;
animation-iteration-count: infinite;
}
&__done {
background: @link-color;
}
&__error {
background: @brand-danger;
}
&__queued {
background: @brand-primary;
}
}
.pulsate-frames() {
.transform(@scaleX, @scaleY) {
-webkit-transform: scale(@scaleX, @scaleY); opacity: 1;
-moz-transform: scale(@scaleX, @scaleY); opacity: 1;
-ms-transform: scale(@scaleX, @scaleY); opacity: 1;
-o-transform: scale(@scaleX, @scaleY); opacity: 1;
transform: scale(@scaleX, @scaleY); opacity: 1;
}
0% {
.transform(1.0, 1.0);
}
25% {
opacity: 1.0;
}
50% {
.transform(0.75, 0.75);
}
50% {
opacity: 1.0;
}
100% {
.transform(1.0, 1.0);
}
}
@-webkit-keyframes pulsate {.pulsate-frames}
@-moz-keyframes pulsate {.pulsate-frames}
@-ms-keyframes pulsate {.pulsate-frames}
@-o-keyframes pulsate {.pulsate-frames}
@keyframes pulsate {.pulsate-frames}

View File

@ -13,16 +13,27 @@ mixin statusText(build)
- var build = this.props.build; - var build = this.props.build;
.build(class="build__#{build.status}") .build(class="")
.build_content .build_content
.build_status
.status(class="status__#{build.status}")
div.build_header div.build_header
if build.project
span
Scm(scm=build.project.scm.type)
|
Link(to="project", params={name: build.project.name})
span= build.project.name
|
if build.number if build.number
span(style={fontSize: '15px', color: '#a6a6a6'}) build
|
if build.status !== 'queued' if build.status !== 'queued'
Link(to="build", params={id: build.id}) Link(to="build", params={id: build.id})
span Build # span #
span= build.number span= build.number
else else
span Build # span #
span= build.number span= build.number
if build.waitReason if build.waitReason
@ -36,12 +47,6 @@ mixin statusText(build)
span ) span )
div div
if build.project
span.build_info
Scm(scm=build.project.scm.type)
|
Link(to="project", params={name: build.project.name})
span= build.project.name
if build.endDate if build.endDate
span.build_info span.build_info
i.fa.fa-fw.fa-clock-o i.fa.fa-fw.fa-clock-o
@ -69,11 +74,13 @@ mixin statusText(build)
.build_controls .build_controls
if build.completed if build.completed
a.btn.btn-sm.btn-default(href="javascript:void(0);", onClick=this.onRebuildProject(build.project.name)) .build_controls_buttons
i.fa.fa-fw.fa-repeat(title="Rebuild") a.btn.btn-sm.btn-default(href="javascript:void(0);", onClick=this.onRebuildProject(build.project.name))
| i.fa.fa-fw.fa-repeat(title="Rebuild")
| Build again |
else | Build again
//-.progress if build.status === 'in-progress'
//-.progress-bar.progress-bar-success(style={width: '60%'}) .build_controls_progress
if build.project.avgBuildDuration
Progress(build=build)

View File

@ -12,6 +12,7 @@ define([
template = template.locals({ template = template.locals({
DateTime: CommonComponents.DateTime, DateTime: CommonComponents.DateTime,
Duration: CommonComponents.Duration, Duration: CommonComponents.Duration,
Progress: CommonComponents.Progress,
Scm: CommonComponents.Scm, Scm: CommonComponents.Scm,
Terminal: TerminalComponent, Terminal: TerminalComponent,
Link: Router.Link Link: Router.Link
@ -24,7 +25,6 @@ define([
}; };
}, },
onRebuildProject: function(projectName) { onRebuildProject: function(projectName) {
console.log('onRebuildProject');
ProjectActions.run(projectName) ProjectActions.run(projectName)
}, },
onShowTerminal: function(build) { onShowTerminal: function(build) {

View File

@ -1,4 +1,9 @@
span span
span= this.props.duration / 1000 if this.props.minutes
span= this.props.minutes
|
span min
|
span= this.state.seconds
| |
span sec span sec

View File

@ -4,6 +4,14 @@ define([
'react', 'templates/app/components/common/duration/index' 'react', 'templates/app/components/common/duration/index'
], function(React, template) { ], function(React, template) {
return React.createClass({ return React.createClass({
render: template render: template,
getInitialState: function() {
var seconds = Math.round(this.props.duration / 1000);
return {
minutes: null,
seconds: seconds
}
}
}); });
}); });

View File

@ -3,11 +3,13 @@
define([ define([
'./dateTime/index', './dateTime/index',
'./scm/index', './scm/index',
'./duration/index' './duration/index',
], function(DateTime, Scm, Duration) { './progress/index'
], function(DateTime, Scm, Duration, Progress) {
return { return {
DateTime: DateTime, DateTime: DateTime,
Scm: Scm, Scm: Scm,
Duration: Duration Duration: Duration,
Progress: Progress
}; };
}); });

View File

@ -0,0 +1,2 @@
.progress
.progress-bar.progress-bar-success(style={width: this.state.percent + '%'})

View File

@ -0,0 +1,31 @@
'use strict';
define([
'underscore',
'react',
'templates/app/components/common/progress/index'
], function(_, React, template) {
return React.createClass({
render: template,
_computePercent: function() {
var build = this.props.build;
return Math.round((Date.now() - build.startDate) /
build.project.avgBuildDuration * 100);
},
componentDidMount: function() {
var self = this,
updateCallback = function() {
if (self.props.build.status === 'in-progress') {
self.setState({percent: self._computePercent()});
_.delay(updateCallback, 100);
}
}
updateCallback();
},
getInitialState: function() {
return {
percent: this._computePercent()
}
}
});
});