merge and bootstrap added

This commit is contained in:
Vladimir Polyakov 2015-05-08 00:55:40 +03:00
commit 1a91f9cb27
34 changed files with 352 additions and 93 deletions

View File

@ -5,6 +5,7 @@ var EventEmitter = require('events').EventEmitter,
function Command(params) {
params = params || {};
this.emitIn = params.emitIn;
this.emitOut = params.emitOut;
}

View File

@ -3,7 +3,7 @@
var spawn = require('child_process').spawn,
ParentCommand = require('./base').Command,
inherits = require('util').inherits,
utils = require('../utils');
_ = require('underscore');
function Command(params) {
params = params || {};
@ -21,20 +21,29 @@ inherits(Command, ParentCommand);
Command.prototype.run = function(params, callback) {
var self = this,
stdout = self.collectOut ? '' : null;
if (!params.cmd) return callback(new Error('`cmd` is not set'));
if (!params.args) return callback(new Error('`args` is not set'));
callback = utils.once(callback);
callback = _(callback).once();
params.options = params.options || {};
params.options.cwd = params.options.cwd || this.cwd;
var cmd = spawn(params.cmd, params.args, params.options);
if (self.emitIn) {
self.emit('stdin', params.cmd + ' ' + params.args.join(' '));
}
cmd.stdout.on('data', function(data) {
if (self.emitOut) self.emit('stdout', data);
if (self.collectOut) stdout += data;
});
cmd.stderr.on('data', function(data) {
callback(new Error('Spawned command outputs to stderr: ' + data));
cmd.kill();
});
cmd.on('close', function(code) {
var err = null;
if (code !== 0) err = new Error(
@ -42,5 +51,6 @@ Command.prototype.run = function(params, callback) {
);
callback(err, stdout);
});
return cmd;
};

View File

@ -17,6 +17,9 @@ function Distributor(params) {
self.onBuildUpdate = params.onBuildUpdate || function(build, callback) {
callback(null, build);
};
self.onBuildData = params.onBuildData || function(build, data) {
};
}
exports.Distributor = Distributor;
@ -55,10 +58,22 @@ Distributor.prototype._runNext = function(callback) {
self.queue.splice(queueItemIndex, 1);
var stepCallback = this.slot();
node.run(queueItem.project, build.params, function(err) {
var executor = node.run(queueItem.project, build.params, function(err) {
build.status = err ? 'error' : 'done';
build.error = err;
self._updateBuild(build, stepCallback);
self._updateBuild(build, function(err, build) {
// try to run next project from the queue
self._runNext(stepCallback);
});
});
executor.on('currentStep', function(stepLabel) {
build.currentStep = stepLabel;
self._updateBuild(build);
});
executor.on('data', function(data) {
self.onBuildData(build, data);
});
},
callback
@ -66,6 +81,7 @@ Distributor.prototype._runNext = function(callback) {
};
Distributor.prototype._updateBuild = function(build, callback) {
callback = callback || _.noop;
this.onBuildUpdate(build, callback);
};
@ -76,7 +92,7 @@ Distributor.prototype.run = function(project, params, callback) {
self._updateBuild({
project: project,
params: params,
status: 'waiting'
status: 'queued'
}, this.slot());
},
function(err, build) {

View File

@ -2,7 +2,10 @@
var Steppy = require('twostep').Steppy,
path = require('path'),
_ = require('underscore');
_ = require('underscore'),
EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits,
utils = require('../utils');
function Executor(params) {
this.project = params.project;
@ -11,6 +14,8 @@ function Executor(params) {
exports.Executor = Executor;
inherits(Executor, EventEmitter);
Executor.prototype._getSources = function(params, callback) {
};
@ -23,11 +28,14 @@ Executor.prototype.run = function(params, callback) {
project = _({}).extend(self.project, params);
Steppy(
function() {
self.emit('currentStep', 'get sources');
self._getSources(project.scm, this.slot());
},
function() {
var funcs = project.steps.map(function(step) {
var funcs = project.steps.map(function(step, index) {
return function() {
var stepLabel = step.name || utils.prune(step.cmd, 15);
self.emit('currentStep', stepLabel);
self._runStep(step, this.slot());
};
});

View File

@ -5,7 +5,8 @@ var Steppy = require('twostep').Steppy,
ParentExecutor = require('./base').Executor,
createScm = require('../scm').createScm,
createCommand = require('../command').createCommand,
fs = require('fs');
fs = require('fs'),
_ = require('underscore');
function Executor(params) {
ParentExecutor.call(this, params);
@ -61,7 +62,21 @@ Executor.prototype._runStep = function(params, callback) {
}
// set command cwd to executor cwd
params.cwd = self.cwd;
var command = createCommand(params);
var command = createCommand(
_({
emitIn: true,
emitOut: true
}).extend(params)
);
command.on('stdin', function(data) {
self.emit('data', '> ' + String(data));
});
command.on('stdout', function(data) {
self.emit('data', String(data));
});
command.run(params, this.slot())
},
callback

View File

@ -39,4 +39,6 @@ Node.prototype.run = function(project, params, callback) {
delete self.executors[project.name];
callback(err);
});
return this.executors[project.name];
};

View File

@ -1,24 +1,12 @@
'use strict';
['Function', 'String', 'Number', 'Date', 'RegExp'].forEach(function(name) {
exports['is' + name] = function(obj) {
return toString.call(obj) == '[object ' + name + ']';
};
});
exports.prune = function(str, length) {
var result = '',
words = str.split(' ');
exports.isObject = function(obj) {
return obj === Object(obj);
};
exports.noop = function() {};
exports.slice = Array.prototype.slice;
exports.once = function(func) {
var isCalled = false;
return function() {
if (isCalled) return;
func.apply(this, arguments);
isCalled = true;
};
do {
result += words.shift() + ' ';
} while (words.length && result.length < length);
return result.replace(/ $/, result.length <= length ? '' : '...');
};

View File

@ -6,7 +6,11 @@
"rev": "default"
},
"steps": [
{"type": "shell", "cmd": "sleep 2 && echo \"hello, cur dir is `pwd`\""},
{"type": "shell", "name": "sleep", "cmd": "sleep 4"},
{"type": "shell", "cmd": "echo 1 > 1.txt"},
{"type": "shell", "cmd": "echo 2 > 2.txt"}
{"type": "shell", "cmd": "sleep 4"},
{"type": "shell", "cmd": "echo 2 > 2.txt"},
{"type": "shell", "cmd": "cat 1.txt 2.txt"}
]
}

View File

@ -7,6 +7,8 @@
},
"steps": [
{"type": "shell", "cmd": "echo 11 > 11.txt"},
{"type": "shell", "cmd": "echo 22 > 22.txt"}
{"type": "shell", "cmd": "sleep 4"},
{"type": "shell", "cmd": "echo 22 > 22.txt"},
{"type": "shell", "cmd": "cat 11.txt 22.txt"}
]
}

View File

@ -19,16 +19,28 @@ project.loadAll('projects', function(err, loadedProjects) {
});
module.exports = function(app) {
var buildsSequnce = 0;
var distributor = new Distributor({
nodes: [{type: 'local', maxExecutorsCount: 1}],
onBuildUpdate: function(build, callback) {
var buildsResource = app.dataio.resource('builds');
if (build.status === 'queued') {
build.id = ++buildsSequnce;
// create resource for build data
var buildDataResource = app.dataio.resource('build' + build.id);
buildDataResource.on('connection', function(client) {
client.emit('sync', 'data', '< collected data >');
});
}
buildsResource.clientEmitSync(
build.status === 'waiting' ? 'create' : 'update',
build.status === 'queued' ? 'create' : 'update',
build
);
callback(null, build);
},
onBuildData: function(build, data) {
app.dataio.resource('build' + build.id).clientEmitSync('data', data);
}
});

View File

@ -0,0 +1,10 @@
'use strict';
define(['reflux'], function(Reflux) {
var Actions = Reflux.createActions([
'readConsoleOutput',
'readAll'
]);
return Actions;
});

View File

@ -6,31 +6,9 @@ define([
], function(
React, template, Components, ProjectActions
) {
//var projectsTemplate = _($('#projects-template').html()).template();
//$('#content').on('click', '.js-projects .js-run', function() {
//var projectName = $(this).parent('.js-project').data('name');
//projects.sync('run', {projectName: projectName}, function(err, result) {
//$('#content').append(
//(err && err.message)
//);
//});
//});
//projects.sync('read', function(err, projects) {
//console.log('read complete');
////$('#content').html(
////(err && err.message) ||
////projectsTemplate({projects: projects})
////);
//});
//builds.subscribe(function(data, action) {
//$('#content').append(action.action + ': ' + JSON.stringify(data));
//});
React.render(template({
App: Components.App
}), document.getElementById('react-content'));
}), document.getElementById('content'));
ProjectActions.readAll();
});

View File

@ -1,6 +1,11 @@
div
h1 application
h1 nci
.row
.col-md-4
ProjectsList()
.col-md-4
BuildsList()
.col-md-4
Console()

View File

@ -3,12 +3,16 @@
define([
'react',
'app/components/projects/index',
'app/components/builds/index',
'app/components/console/index',
'templates/app/components/app'
], function(React, Projects, template) {
], function(React, Projects, Builds, Console, template) {
var Component = React.createClass({
render: function() {
return template({
ProjectsList: Projects.List
ProjectsList: Projects.List,
BuildsList: Builds.List,
Console: Console.Console
});
}
});

View File

@ -0,0 +1,11 @@
'use strict';
define([
'app/components/builds/item',
'app/components/builds/list'
], function(Item, List) {
return {
Item: Item,
List: List
};
});

View File

@ -0,0 +1,5 @@
li.list-group-item
span.badge= item.status
a.pull-right(href="javascript:void(0);", onClick=onBuildSelect(item.id), style={marginRight: '5px'}) show console output
span Build #
span= item.id

View File

@ -0,0 +1,20 @@
'use strict';
define([
'react', 'app/actions/build', 'templates/app/components/builds/item'
], function(React, BuildActions, template) {
var Component = React.createClass({
onBuildSelect: function(buildId) {
console.log('on build select');
BuildActions.readConsoleOutput(buildId);
},
render: function() {
return template({
item: this.props.item,
onBuildSelect: this.onBuildSelect
});
}
});
return Component;
});

View File

@ -0,0 +1,6 @@
div
h2 builds list
- console.log(items)
ul.list-group
each build, index in items
Item(item=build, key=build.id)

View File

@ -0,0 +1,32 @@
'use strict';
define([
'react',
'reflux',
'./item',
'app/stores/builds',
'templates/app/components/builds/list'
], function(React, Reflux, Item, buildsStore, template) {
var Component = React.createClass({
mixins: [Reflux.ListenerMixin],
componentDidMount: function() {
this.listenTo(buildsStore, this.updateItems);
},
updateItems: function(items) {
this.setState({items: items});
},
render: function() {
return template({
Item: Item,
items: this.state.items
});
},
getInitialState: function() {
return {
items: []
};
}
});
return Component;
});

View File

@ -0,0 +1,3 @@
if name
h2= name
pre= data

View File

@ -0,0 +1,29 @@
'use strict';
define([
'react',
'reflux',
'app/stores/console',
'templates/app/components/console/console'
], function(React, Reflux, consoleStore, template) {
var Component = React.createClass({
mixins: [Reflux.ListenerMixin],
componentDidMount: function() {
this.listenTo(consoleStore, this.updateItems);
},
updateItems: function(data) {
this.setState({data: data});
},
render: function() {
return template(this.state.data);
},
getInitialState: function() {
return {
name: '',
data: ''
};
}
});
return Component;
});

View File

@ -0,0 +1,9 @@
'use strict';
define([
'app/components/console/console',
], function(Console) {
return {
Console: Console
};
});

View File

@ -2,10 +2,12 @@
define([
'app/components/projects/index',
'app/components/builds/index',
'app/components/app',
], function(ProjectsComponents, App) {
], function(ProjectsComponents, BuildsComponents, App) {
return {
App: App,
ProjectsComponents: ProjectsComponents
ProjectsComponents: ProjectsComponents,
BuildsComponents: BuildsComponents
};
});

View File

@ -1 +1,3 @@
h1(onClick=onProjectSelect(item.name))= item.name
li.list-group-item
a.pull-right(href="javascript:void(0);", onClick=onProjectSelect(item.name)) start build
span= item.name

View File

@ -1,4 +1,4 @@
div
h2 projects list
h2 projects list
ul.list-group
each project, index in items
Item(item=project, key=project.name)

8
static/js/app/connect.js Normal file
View File

@ -0,0 +1,8 @@
'use strict';
define([
'socketio', 'dataio'
], function(socketio, dataio) {
// Do it because we use connect in console store
return dataio(socketio.connect());
});

View File

@ -1,2 +1,2 @@
div
.container-fluid
App()

View File

@ -1,10 +1,6 @@
'use strict';
define([
'socketio', 'dataio'
], function(socketio, dataio) {
var connect = dataio(socketio.connect());
define(['app/connect'], function(connect) {
var projects = connect.resource('projects');
var builds = connect.resource('builds');

View File

@ -0,0 +1,37 @@
'use strict';
define([
'underscore',
'reflux', 'app/actions/build', 'app/resources'
], function(_, Reflux, BuildActions, resources) {
var resource = resources.builds;
var Store = Reflux.createStore({
listenables: BuildActions,
builds: [],
_onAction: function(build, action) {
var oldBuild = _(this.builds).findWhere({id: build.id});
if (oldBuild) {
_(oldBuild).extend(build);
} else {
this.builds.unshift(build);
}
this.trigger(this.builds);
},
init: function() {
resource.subscribe(this._onAction);
},
onReadAll: function() {
var self = this;
resource.sync('read', function(err, builds) {
self.trigger(builds);
});
}
});
return Store;
});

View File

@ -0,0 +1,34 @@
'use strict';
define([
'underscore',
'reflux', 'app/actions/build', 'app/connect'
], function(_, Reflux, BuildActions, connect) {
var Store = Reflux.createStore({
listenables: BuildActions,
output: '',
init: function() {
console.log('init builds console output');
},
onReadConsoleOutput: function(buildId) {
this.output = ''
var resourceName = 'build' + buildId,
self = this;
connect.resource(resourceName).unsubscribeAll();
connect.resource(resourceName).subscribe(function(data) {
self.output += data;
self.trigger({
name: 'Console for build #' + buildId,
data: data
});
});
}
});
return Store;
});

19
static/js/dataio.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
define(['_dataio'], function(dataio) {
return function(socket) {
var connect = dataio(socket);
/*
* Extend Resource
*/
var resource = connect.resource('__someResource__'),
resourcePrototype = Object.getPrototypeOf(resource);
resourcePrototype.unsubscribeAll = function() {
this.socket.removeAllListeners();
};
return connect;
};
});

View File

@ -6,7 +6,7 @@ require.config({
underscore: 'libs/underscore/underscore',
react: 'libs/react/react-with-addons',
reflux: 'libs/reflux/dist/reflux',
dataio: '/data.io',
_dataio: '/data.io',
socketio: '/socket.io/socket.io.js',
jquery: 'libs/jquery/jquery'
}

View File

@ -2,7 +2,8 @@
var Distributor = require('../lib/distributor').Distributor,
Node = require('../lib/node').Node,
expect = require('expect.js');
expect = require('expect.js'),
EventEmitter = require('events').EventEmitter;
describe('Distributor', function() {
@ -13,7 +14,9 @@ describe('Distributor', function() {
return function(params) {
var node = new Node(params);
node._createExecutor = function() {
return {run: executorRun};
var executor = new EventEmitter();
executor.run = executorRun;
return executor;
};
return node;
};
@ -43,7 +46,7 @@ describe('Distributor', function() {
it('instance should be created without errors', function() {
var number = 1;
var conditionsHash = {
1: {queue: {length: 0}, build: {status: 'waiting'}},
1: {queue: {length: 0}, build: {status: 'queued'}},
2: {queue: {length: 1}, build: {status: 'in-progress'}},
3: {queue: {length: 0}, build: {status: 'done'}},
4: 'Should never happend'
@ -92,7 +95,7 @@ describe('Distributor', function() {
it('instance should be created without errors', function() {
var number = 1;
var conditionsHash = {
1: {queue: {length: 0}, build: {status: 'waiting'}},
1: {queue: {length: 0}, build: {status: 'queued'}},
2: {queue: {length: 1}, build: {status: 'in-progress'}},
3: {
queue: {length: 0},

View File

@ -1,26 +1,14 @@
doctype html
html
head
title test
title nci
// do it temporary
link(href="/js/libs/bootstrap/dist/css/bootstrap.css", rel="stylesheet", type="text/css")
script(data-main="/js/main" src="/js/libs/requirejs/require.js")
script(type="text/javascript").
require(['app/app']);
body
h1 hello world
script#projects-template(type="text/template")
| <div class="js-projects">
| <% _(projects).each(function(project) { %>
| <div class="js-project" data-name="<%= project.name %>">
| <span><%= project.name %></span>
| <span class="js-run">&rarr;</span>
| </div>
| <% }); %>
| </div>
#content
#react-content