mirror of
https://gitlab.silvrtree.co.uk/martind2000/nci.git
synced 2025-02-10 18:09:15 +00:00
add support of cancellation of queued build
This commit is contained in:
parent
55e5b257e6
commit
5f9b61bcb0
@ -67,4 +67,4 @@ problems detected by regexp) then step will be rerun without error
|
||||
* Ability to change build parameters from ui (at least target branch)
|
||||
* ~~Embedded database (apparently level db)~~
|
||||
* ~~Lightweight (minimal dependencies)~~
|
||||
* Cancel build
|
||||
* ~~Cancel build~~
|
||||
|
@ -14,7 +14,7 @@ http:
|
||||
|
||||
storage:
|
||||
backend: memdown
|
||||
# backend: medeadown
|
||||
# backend: leveldown
|
||||
|
||||
notify:
|
||||
mail:
|
||||
|
@ -33,6 +33,14 @@ exports.init = function(app, callback) {
|
||||
},
|
||||
callback
|
||||
);
|
||||
},
|
||||
removeBuild: function(build, callback) {
|
||||
Steppy(
|
||||
function() {
|
||||
db.builds.del([build.id], this.slot());
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,9 +80,9 @@ exports.init = function(app, callback) {
|
||||
|
||||
exports.createBuildDataResource = createBuildDataResource;
|
||||
|
||||
distributor.on('buildUpdate', function(build, changes) {
|
||||
var buildsResource = app.dataio.resource('builds');
|
||||
var buildsResource = app.dataio.resource('builds');
|
||||
|
||||
distributor.on('buildUpdate', function(build, changes) {
|
||||
if (build.status === 'queued') {
|
||||
createBuildDataResource(build.id);
|
||||
}
|
||||
@ -91,6 +99,10 @@ exports.init = function(app, callback) {
|
||||
});
|
||||
});
|
||||
|
||||
distributor.on('buildCancel', function(build) {
|
||||
buildsResource.clientEmitSync('cancel', {buildId: build.id});
|
||||
});
|
||||
|
||||
var buildLogLineNumbersHash = {};
|
||||
|
||||
distributor.on('buildData', function(build, data) {
|
||||
|
@ -22,6 +22,10 @@ function Distributor(params) {
|
||||
callback(null, build);
|
||||
};
|
||||
|
||||
self.removeBuild = params.removeBuild || function(build, callback) {
|
||||
callback();
|
||||
};
|
||||
|
||||
self.projects = params.projects;
|
||||
}
|
||||
|
||||
@ -209,6 +213,36 @@ Distributor.prototype._updateBuild = function(build, changes, callback) {
|
||||
);
|
||||
};
|
||||
|
||||
Distributor.prototype.cancel = function(params, callback) {
|
||||
var self = this;
|
||||
Steppy(
|
||||
function() {
|
||||
var queueItemIndex = _(self.queue).findIndex(function(item) {
|
||||
return item.build.id === params.buildId;
|
||||
});
|
||||
|
||||
if (queueItemIndex === -1) {
|
||||
throw new Error(
|
||||
'Build with id "' + params.buildId + '" not found for cancel'
|
||||
);
|
||||
}
|
||||
|
||||
// only queued build are in the queue, so there is no reason
|
||||
// to check status
|
||||
var build = self.queue[queueItemIndex].build;
|
||||
|
||||
// remove from queue
|
||||
self.queue.splice(queueItemIndex, 1)[0];
|
||||
|
||||
// remove from db
|
||||
self.removeBuild(build, this.slot());
|
||||
|
||||
self.emit('buildCancel', build);
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
Distributor.prototype.run = function(params, callback) {
|
||||
var self = this,
|
||||
project;
|
||||
|
@ -3,10 +3,12 @@
|
||||
var Steppy = require('twostep').Steppy,
|
||||
_ = require('underscore'),
|
||||
db = require('../db'),
|
||||
utils = require('../lib/utils');
|
||||
utils = require('../lib/utils'),
|
||||
logger = require('../lib/logger')('builds resource');
|
||||
|
||||
module.exports = function(app) {
|
||||
var resource = app.dataio.resource('builds');
|
||||
var resource = app.dataio.resource('builds'),
|
||||
distributor = app.distributor;
|
||||
|
||||
resource.use('readAll', function(req, res, next) {
|
||||
Steppy(
|
||||
@ -104,5 +106,19 @@ module.exports = function(app) {
|
||||
);
|
||||
});
|
||||
|
||||
resource.use('cancel', function(req, res, next) {
|
||||
Steppy(
|
||||
function() {
|
||||
var buildId = req.data.buildId;
|
||||
logger.log('Cancel build: "%s"', buildId);
|
||||
distributor.cancel({buildId: buildId}, this.slot());
|
||||
},
|
||||
function() {
|
||||
res.send();
|
||||
},
|
||||
next
|
||||
);
|
||||
});
|
||||
|
||||
return resource;
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
define(['reflux'], function(Reflux) {
|
||||
var Actions = Reflux.createActions([
|
||||
'cancel',
|
||||
'readTerminalOutput',
|
||||
'readAll',
|
||||
'read'
|
||||
|
@ -84,4 +84,10 @@ mixin statusText(build)
|
||||
.build_controls_progress
|
||||
if build.project.avgBuildDuration
|
||||
Progress(build=build)
|
||||
if build.status === 'queued'
|
||||
.build_controls_buttons
|
||||
a.btn.btn-sm.btn-default(href="javascript:void(0);", onClick=this.onCancelBuild(build.id))
|
||||
i.fa.fa-fw.fa-times(title="Cancel build")
|
||||
|
|
||||
| Cancel build
|
||||
|
||||
|
@ -30,6 +30,9 @@ define([
|
||||
onRebuildProject: function(projectName) {
|
||||
ProjectActions.run(projectName)
|
||||
},
|
||||
onCancelBuild: function(buildId) {
|
||||
BuildActions.cancel(buildId);
|
||||
},
|
||||
onShowTerminal: function(build) {
|
||||
this.setState({showTerminal: !this.state.showTerminal});
|
||||
BuildActions.readTerminalOutput(this.props.build);
|
||||
|
@ -14,7 +14,7 @@ define([
|
||||
return this.builds;
|
||||
},
|
||||
|
||||
onChange: function(data, action) {
|
||||
onChanged: function(data) {
|
||||
var oldBuild = _(this.builds).findWhere({id: data.buildId});
|
||||
if (oldBuild) {
|
||||
_(oldBuild).extend(data.changes);
|
||||
@ -27,8 +27,23 @@ define([
|
||||
this.trigger(this.builds);
|
||||
},
|
||||
|
||||
onCancelled: function(data) {
|
||||
// WORKAROUND: client that trigger `onCancel` gets one `onCancelled`
|
||||
// call other clients get 2 calls (second with empty data)
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
var index = _(this.builds).findIndex({id: data.buildId});
|
||||
if (index !== -1) {
|
||||
this.builds.splice(index, 1);
|
||||
}
|
||||
|
||||
this.trigger(this.builds);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
resource.subscribe('change', this.onChange);
|
||||
resource.subscribe('change', this.onChanged);
|
||||
resource.subscribe('cancel', this.onCancelled);
|
||||
},
|
||||
|
||||
onReadAll: function(params) {
|
||||
@ -39,6 +54,12 @@ define([
|
||||
self.trigger(self.builds);
|
||||
});
|
||||
},
|
||||
|
||||
onCancel: function(buildId) {
|
||||
resource.sync('cancel', {buildId: buildId}, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Store;
|
||||
|
@ -128,4 +128,99 @@ describe('Distributor main', function() {
|
||||
Distributor.prototype._createNode.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with success project and build cancel', function() {
|
||||
before(function() {
|
||||
sinon.stub(Distributor.prototype, '_createNode', createNodeMock(
|
||||
sinon.stub().callsArgAsync(1)
|
||||
));
|
||||
});
|
||||
|
||||
var distributorParams = {
|
||||
projects: projects,
|
||||
nodes: [{type: 'local', maxExecutorsCount: 1}],
|
||||
saveBuild: function(build, callback) {
|
||||
build.id = 1;
|
||||
callback(null, build);
|
||||
}
|
||||
};
|
||||
|
||||
describe('when cancel queued buid', function() {
|
||||
var updateBuildSpy;
|
||||
|
||||
var cancelError;
|
||||
it('instance should be created without errors', function() {
|
||||
distributor = new Distributor(distributorParams);
|
||||
|
||||
var originalRunNext = distributor._runNext;
|
||||
distributor._runNext = function() {
|
||||
distributor.cancel({buildId: 1}, function(err) {
|
||||
cancelError = err;
|
||||
});
|
||||
originalRunNext.apply(distributor, arguments);
|
||||
};
|
||||
|
||||
updateBuildSpy = sinon.spy(distributor, '_updateBuild');
|
||||
});
|
||||
|
||||
it('should run without errors', function(done) {
|
||||
distributor.run({projectName: 'project1'}, function(err) {
|
||||
expect(err).not.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('build should be queued', function() {
|
||||
var changes = updateBuildSpy.getCall(0).args[1];
|
||||
expect(changes).only.have.keys(
|
||||
'project', 'initiator', 'params', 'createDate', 'status',
|
||||
'completed'
|
||||
);
|
||||
expect(changes.status).equal('queued');
|
||||
expect(changes.completed).equal(false);
|
||||
});
|
||||
|
||||
it('should be cancelled without error', function() {
|
||||
expect(cancelError).not.ok();
|
||||
});
|
||||
|
||||
it('update build called only once', function() {
|
||||
expect(updateBuildSpy.callCount).equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when try to cancel unexisted build', function() {
|
||||
var cancelError;
|
||||
|
||||
it('instance should be created without errors', function() {
|
||||
distributor = new Distributor(distributorParams);
|
||||
|
||||
var originalRunNext = distributor._runNext;
|
||||
distributor._runNext = function() {
|
||||
distributor.cancel({buildId: 2}, function(err) {
|
||||
cancelError = err;
|
||||
});
|
||||
originalRunNext.apply(distributor, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
it('should run without errors', function(done) {
|
||||
distributor.run({projectName: 'project1'}, function(err) {
|
||||
expect(err).not.ok();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be cancelled with error (build not found)', function() {
|
||||
expect(cancelError).ok();
|
||||
expect(cancelError.message).eql(
|
||||
'Build with id "2" not found for cancel'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
after(function() {
|
||||
Distributor.prototype._createNode.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user