progress bar and project status

This commit is contained in:
Vladimir Polyakov 2015-07-26 16:05:54 +03:00
parent a8e9516546
commit f6a1a7bfe1
10 changed files with 192 additions and 44 deletions

View File

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

View File

@ -5,6 +5,7 @@ var Steppy = require('twostep').Steppy,
path = require('path'),
_ = require('underscore'),
reader = require('./reader'),
db = require('../db'),
utils = require('./utils');
@ -99,3 +100,27 @@ exports.create = function(baseDir, config, 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) {
delete build.stepTimings;
delete build.scm.changes;
build.project = _(build.project).pick('name', 'scm');
build.project = _(build.project).pick(
'name', 'scm', 'avgBuildDuration'
);
});
res.send(builds);

View File

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

View File

@ -14,15 +14,12 @@
.row();
padding: 15px 0;
margin-bottom: 3px;
background: lighten(@well-bg, 3%);
&__in-progress {
background: lighten(@brand-info, 40%);
}
&__done {
background: lighten(@brand-success, 50%);
}
&__error {
background: lighten(@brand-danger, 30%);
&_status {
float: left;
padding-top: 14px;
margin-right: 13px;
}
&_info {
@ -34,8 +31,21 @@
.make-md-column(3);
.make-xs-column(3);
.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 {
.make-md-column(9);
.make-xs-column(9);
@ -48,4 +58,72 @@
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;
.build(class="build__#{build.status}")
.build(class="")
.build_content
.build_status
.status(class="status__#{build.status}")
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
span(style={fontSize: '15px', color: '#a6a6a6'}) build
|
if build.status !== 'queued'
Link(to="build", params={id: build.id})
span Build #
span #
span= build.number
else
span Build #
span #
span= build.number
if build.waitReason
@ -36,12 +47,6 @@ mixin statusText(build)
span )
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
span.build_info
i.fa.fa-fw.fa-clock-o
@ -69,11 +74,13 @@ mixin statusText(build)
.build_controls
if build.completed
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
//-.progress
//-.progress-bar.progress-bar-success(style={width: '60%'})
.build_controls_buttons
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
if build.status === 'in-progress'
.build_controls_progress
if build.project.avgBuildDuration
Progress(build=build)

View File

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

View File

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