This commit is contained in:
oleg 2015-12-03 22:08:53 +03:00
commit 459825e20b
7 changed files with 141 additions and 51 deletions

View File

@ -0,0 +1,9 @@
scm:
type: git
repository: /Users/vladimir/projects/fit/culture/frontend_node
rev: master
steps:
sync: npm run sync
test: nrun test -R dot
pack: nrun packSafe

View File

@ -1,7 +1,7 @@
scm: scm:
type: git type: git
repository: ./ repository: ../../nci
rev: master rev: master
# catchRev: # catchRev:

View File

@ -103,32 +103,58 @@ exports.init = function(app, callback) {
buildsResource.clientEmitSync('cancel', {buildId: build.id}); buildsResource.clientEmitSync('cancel', {buildId: build.id});
}); });
var buildLogLineNumbersHash = {}; var buildLogLineNumbersHash = {},
lastLinesHash = {};
distributor.on('buildData', function(build, data) { distributor.on('buildData', function(build, data) {
var lines = _(data.split('\n')).chain().map(function(line) { var cleanupText = function(text) {
return line.replace('\r', ''); return text.replace('\r', '');
}).compact().value(), };
var splittedData = data.split('\n'),
logLineNumber = buildLogLineNumbersHash[build.id] || 0; logLineNumber = buildLogLineNumbersHash[build.id] || 0;
lines = _(lines).map(function(line, index) { lastLinesHash[build.id] = lastLinesHash[build.id] || '';
return {
number: logLineNumber + index,
text: line
};
});
buildLogLineNumbersHash[build.id] = logLineNumber + lines.length;
// if we don't have last line, so we start new line
if (!lastLinesHash[build.id]) {
logLineNumber++;
}
lastLinesHash[build.id] += _(splittedData).first();
var lines = [{
text: cleanupText(lastLinesHash[build.id]),
buildId: build.id,
number: logLineNumber
}];
if (splittedData.length > 1) {
// if we have last '' we have to take all except last
// this shown that string ends with eol
if (_(splittedData).last() === '') {
lastLinesHash[build.id] = '';
splittedData = _(splittedData.slice(1)).initial();
} else {
lastLinesHash[build.id] = _(splittedData).last();
splittedData = _(splittedData).tail();
}
lines = lines.concat(_(splittedData).map(function(line) {
return {
text: cleanupText(line),
buildId: build.id,
number: ++logLineNumber
};
}));
}
buildLogLineNumbersHash[build.id] = logLineNumber;
app.dataio.resource('build' + build.id).clientEmitSync( app.dataio.resource('build' + build.id).clientEmitSync(
'data', 'data',
{lines: lines} {lines: lines}
); );
_(lines).each(function(line) {
line.buildId = build.id;
});
// write build logs to db // write build logs to db
if (lines.length) {
db.logLines.put(lines, function(err) { db.logLines.put(lines, function(err) {
if (err) { if (err) {
logger.error( logger.error(
@ -138,7 +164,6 @@ exports.init = function(app, callback) {
); );
} }
}); });
}
}); });
callback(null, distributor); callback(null, distributor);

View File

@ -8,7 +8,6 @@
&_code { &_code {
clear: left; clear: left;
height: 500px;
min-height: 42px; min-height: 42px;
padding: 15px 0; padding: 15px 0;
color: #F1F1F1; color: #F1F1F1;

View File

@ -1,6 +1,3 @@
.terminal .terminal
pre.terminal_code(ref="code") pre.terminal_code
each row, index in this.state.data .terminal_footer(style={height: '30px'})
.code-line(key=index)
span.code-line_counter
.code-line_body!= row

View File

@ -6,42 +6,96 @@ define([
'reflux', 'reflux',
'app/stores/terminal', 'app/stores/terminal',
'ansi_up', 'ansi_up',
'templates/app/components/terminal/terminal' 'templates/app/components/terminal/terminal',
], function(_, React, Reflux, terminalStore, ansiUp, template) { ], function(_, React, Reflux, terminalStore, ansiUp, template) {
var Component = React.createClass({ var Component = React.createClass({
mixins: [Reflux.ListenerMixin], mixins: [Reflux.ListenerMixin],
shouldScrollBottom: true, shouldScrollBottom: true,
ignoreScrollEvent: false, data: [],
linesCount: 0,
componentDidMount: function() { componentDidMount: function() {
this.listenTo(terminalStore, this.updateItems); this.listenTo(terminalStore, this.updateItems);
var node = document.getElementsByClassName('terminal')[0];
this.initialScrollPosition = node.getBoundingClientRect().top;
window.onscroll = this.onScroll;
},
componentWillUnmount: function() {
window.onscroll = null;
},
prepareRow: function(row) {
return ansiUp.ansi_to_html(row.replace('\r', ''));
}, },
prepareOutput: function(output) { prepareOutput: function(output) {
var self = this;
return output.map(function(row) { return output.map(function(row) {
return ansiUp.ansi_to_html(row.text); return self.prepareRow(row);
}); });
}, },
componentWillUpdate: function() { getTerminal: function() {
var node = this.refs.code.getDOMNode(); return document.getElementsByClassName('terminal')[0];
this.shouldScrollBottom = node.scrollTop + node.offsetHeight >= node.scrollHeight;
}, },
componentDidUpdate: function() { getBody: function() {
return document.getElementsByTagName('body')[0];
},
onScroll: function() {
var node = this.getTerminal(),
body = this.getBody();
this.shouldScrollBottom = window.innerHeight + body.scrollTop >=
node.offsetHeight + this.initialScrollPosition;
},
ensureScrollPosition: function() {
if (this.shouldScrollBottom) { if (this.shouldScrollBottom) {
var node = this.refs.code.getDOMNode(); var node = this.getTerminal(),
node.scrollTop = node.scrollHeight; body = this.getBody();
body.scrollTop = this.initialScrollPosition + node.offsetHeight;
} }
}, },
makeCodeLineContent: function(line) {
return '<span class="code-line_counter">' + '</span>' +
'<div class="code-line_body">' + this.prepareRow(line) + '</div>';
},
makeCodeLine: function(line, index) {
return '<div class="code-line" data-number="' + index + '">' +
this.makeCodeLineContent(line) + '</div>';
},
renderBuffer: _.throttle(function() {
var data = this.data,
currentLinesCount = data.length,
terminal = document.getElementsByClassName('terminal_code')[0],
rows = terminal.childNodes;
if (rows.length) {
// replace our last node
var index = this.linesCount - 1;
rows[index].innerHTML = this.makeCodeLineContent(data[index]);
}
var self = this;
terminal.insertAdjacentHTML('beforeend',
_(data.slice(this.linesCount)).map(function(line, index) {
return self.makeCodeLine(line, self.linesCount + index);
}).join('')
);
this.linesCount = currentLinesCount;
this.ensureScrollPosition();
}, 100),
updateItems: function(build) { updateItems: function(build) {
// listen just our console update // listen just our console update
if (build.buildId === this.props.build) { if (build.buildId === this.props.build) {
this.setState({data: this.prepareOutput(build.data)}); this.data = build.data;
this.renderBuffer();
} }
}, },
render: template, shouldComponentUpdate: function() {
getInitialState: function() { return false;
return { },
data: [] render: template
};
}
}); });
return Component; return Component;

View File

@ -29,16 +29,22 @@ define([
} }
connect.resource(resourceName).subscribe('data', function(data) { connect.resource(resourceName).subscribe('data', function(data) {
output = output.concat(data.lines); var lastLine = _(self.lines).last();
if (lastLine && (_(data.lines).first().number === lastLine.number)) {
self.lines = _(self.lines).initial();
}
self.lines = self.lines.concat(data.lines);
self.trigger({ self.trigger({
buildId: build.id, buildId: build.id,
name: 'Console for build #' + build.id, name: 'Console for build #' + build.id,
data: output data: _(self.lines).pluck('text')
}); });
}); });
}; };
this.lines = [];
this.currentLine = '';
// create data resource for completed build // create data resource for completed build
if (build.completed) { if (build.completed) {
connect.resource('projects').sync( connect.resource('projects').sync(