Skip to content

Commit 0794872

Browse files
committed
Implement Logger#logTable()
1 parent 7eb0499 commit 0794872

File tree

5 files changed

+186
-45
lines changed

5 files changed

+186
-45
lines changed

bin/concurrently.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ describe('--timings', () => {
400400
const expectLinesForTimingsTable = (lines) => {
401401
const tableTopBorderRegex = /[]+/g;
402402
expect(lines).toContainEqual(expect.stringMatching(tableTopBorderRegex));
403-
const tableHeaderRowRegex = /(\W+(\(index\)|name|duration|exit code|killed|command)\W+){6}/g;
403+
const tableHeaderRowRegex = /(\W+(name|duration|exit code|killed|command)\W+){5}/g;
404404
expect(lines).toContainEqual(expect.stringMatching(tableHeaderRowRegex));
405405
const tableBottomBorderRegex = /[]+/g;
406406
expect(lines).toContainEqual(expect.stringMatching(tableBottomBorderRegex));

src/flow-control/log-timings.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const Rx = require('rxjs');
2-
const _ = require('lodash');
31
const formatDate = require('date-fns/format');
2+
const Rx = require('rxjs');
43
const { bufferCount, take } = require('rxjs/operators');
4+
const _ = require('lodash');
55
const BaseHandler = require('./base-handler');
66

77
module.exports = class LogTimings extends BaseHandler {
@@ -13,7 +13,7 @@ module.exports = class LogTimings extends BaseHandler {
1313

1414
printExitInfoTimingTable(exitInfos) {
1515
const exitInfoTable = _(exitInfos)
16-
.sortBy(({timings}) => timings.durationSeconds)
16+
.sortBy(({ timings }) => timings.durationSeconds)
1717
.reverse()
1818
.map(({ command, timings, killed, exitCode }) => {
1919
const readableDurationMs = (timings.endDate - timings.startDate).toLocaleString();
@@ -27,40 +27,38 @@ module.exports = class LogTimings extends BaseHandler {
2727
})
2828
.value();
2929

30-
console.log('\nTimings:');
31-
console.table(exitInfoTable);
30+
this.logger.logGlobalEvent('Timings:');
31+
this.logger.logTable(exitInfoTable);
3232
return exitInfos;
3333
};
3434

3535
handle(commands) {
36-
if (!this.logger) { return {commands}; }
37-
38-
const controllerInstance = this;
36+
if (!this.logger) {
37+
return { commands };
38+
}
3939

4040
// individual process timings
4141
commands.forEach(command => {
42-
command.timer.subscribe( {
43-
next: ({startDate, endDate}) => {
44-
if (!endDate) {
45-
controllerInstance.logger.logCommandEvent( `${ command.command } started at ${ formatDate(startDate, controllerInstance.timestampFormat) }`, command );
46-
} else {
47-
const durationMs = (endDate.getTime() - startDate.getTime());
48-
49-
controllerInstance.logger.logCommandEvent( `${ command.command } stopped at ${ formatDate(endDate, controllerInstance.timestampFormat) } after ${durationMs.toLocaleString()}ms`, command );
50-
}
51-
},
52-
} );
42+
command.timer.subscribe(({ startDate, endDate }) => {
43+
if (!endDate) {
44+
const formattedStartDate = formatDate(startDate, this.timestampFormat);
45+
this.logger.logCommandEvent(`${command.command} started at ${formattedStartDate}`, command);
46+
} else {
47+
const durationMs = endDate.getTime() - startDate.getTime();
48+
const formattedEndDate = formatDate(endDate, this.timestampFormat);
49+
this.logger.logCommandEvent(`${command.command} stopped at ${formattedEndDate} after ${durationMs.toLocaleString()}ms`, command);
50+
}
51+
});
5352
});
5453

5554
// overall summary timings
5655
const closeStreams = commands.map(command => command.close);
57-
this.allProcessesClosed = Rx.merge(...closeStreams)
58-
.pipe(
59-
bufferCount(closeStreams.length),
60-
take(1),
61-
);
56+
this.allProcessesClosed = Rx.merge(...closeStreams).pipe(
57+
bufferCount(closeStreams.length),
58+
take(1),
59+
);
6260
this.allProcessesClosed.subscribe((exitInfos) => this.printExitInfoTimingTable(exitInfos));
6361

64-
return {commands};
62+
return { commands };
6563
}
6664
};

src/flow-control/log-timings.spec.js

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -104,43 +104,34 @@ it('logs the timings at the start and end (ie complete or error) event of each c
104104
});
105105

106106
it('does not log timings summary if there was an error', () => {
107-
jest.spyOn(console, 'table').mockImplementation(() => {});
108-
109107
controller.handle(commands);
110108

111109
commands[0].close.next(command0ExitInfo);
112110
commands[1].error.next();
113111

114-
expect(console.table).toHaveBeenCalledTimes(0);
112+
expect(logger.logTable).toHaveBeenCalledTimes(0);
115113

116114
});
117115

118116
it('logs the sorted timings summary when all processes close successfully', () => {
119-
jest.spyOn(console, 'log').mockImplementation(() => {});
120-
jest.spyOn(console, 'table').mockImplementation(() => {});
121117
jest.spyOn(controller, 'printExitInfoTimingTable');
122-
123118
controller.handle(commands);
124119

125120
commands[0].close.next(command0ExitInfo);
126121
commands[1].close.next(command1ExitInfo);
127122

128-
expect(console.table).toHaveBeenCalledTimes(1);
123+
expect(logger.logTable).toHaveBeenCalledTimes(1);
129124

130125
// un-sorted ie by finish order
131-
expect(controller.printExitInfoTimingTable).toHaveBeenCalledWith(
132-
[
133-
command0ExitInfo,
134-
command1ExitInfo
135-
],
136-
);
126+
expect(controller.printExitInfoTimingTable).toHaveBeenCalledWith([
127+
command0ExitInfo,
128+
command1ExitInfo
129+
]);
137130

138131
// sorted by duration
139-
expect(console.table).toHaveBeenCalledWith(
140-
[
141-
exitInfoToTimingInfo(command1ExitInfo),
142-
exitInfoToTimingInfo(command0ExitInfo),
143-
],
144-
);
132+
expect(logger.logTable).toHaveBeenCalledWith([
133+
exitInfoToTimingInfo(command1ExitInfo),
134+
exitInfoToTimingInfo(command0ExitInfo)
135+
]);
145136

146137
});

src/logger.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,63 @@ module.exports = class Logger {
9696
this.log(chalk.reset('-->') + ' ', chalk.reset(text) + '\n');
9797
}
9898

99+
logTable(tableContents) {
100+
// For now, can only print array tables with some content.
101+
if (this.raw || !Array.isArray(tableContents) || !tableContents.length) {
102+
return;
103+
}
104+
105+
let nextColIndex = 0;
106+
const headers = {};
107+
const contentRows = tableContents.map(row => {
108+
const rowContents = [];
109+
Object.keys(row).forEach((col) => {
110+
if (!headers[col]) {
111+
headers[col] = {
112+
index: nextColIndex++,
113+
//
114+
length: col.length,
115+
};
116+
}
117+
118+
const colIndex = headers[col].index;
119+
const formattedValue = String(row[col] == null ? '' : row[col]);
120+
// Update the column length in case this rows value is longer than the previous length for the column.
121+
headers[col].length = Math.max(formattedValue.length, headers[col].length);
122+
rowContents[colIndex] = formattedValue;
123+
return rowContents;
124+
});
125+
return rowContents;
126+
});
127+
128+
const headersFormatted = Object
129+
.keys(headers)
130+
.map(header => header.padEnd(headers[header].length, ' '));
131+
132+
if (!headersFormatted.length) {
133+
// No columns exist.
134+
return;
135+
}
136+
137+
const borderRowFormatted = headersFormatted.map(header => '─'.padEnd(header.length, '─'));
138+
139+
this.logGlobalEvent(`┌─${borderRowFormatted.join('─┬─')}─┐`);
140+
this.logGlobalEvent(`│ ${headersFormatted.join(' │ ')} │`);
141+
this.logGlobalEvent(`├─${borderRowFormatted.join('─┼─')}─┤`);
142+
143+
contentRows.forEach(contentRow => {
144+
const contentRowFormatted = headersFormatted.map((header, colIndex) => {
145+
// If the table was expanded after this row was processed, it won't have this column.
146+
// Use an empty string in this case.
147+
const col = contentRow[colIndex] || '';
148+
return col.padEnd(header.length, ' ');
149+
});
150+
this.logGlobalEvent(`│ ${contentRowFormatted.join(' │ ')} │`);
151+
});
152+
153+
this.logGlobalEvent(`└─${borderRowFormatted.join('─┴─')}─┘`);
154+
}
155+
99156
log(prefix, text) {
100157
if (this.raw) {
101158
return this.outputStream.write(text);

src/logger.spec.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,98 @@ describe('#logCommandEvent()', () => {
221221
expect(logger.log).toHaveBeenCalledWith(chalk.reset('[1]') + ' ', chalk.reset('foo') + '\n');
222222
});
223223
});
224+
225+
describe('#logTable()', () => {
226+
it('does not log anything in raw mode', () => {
227+
const logger = createLogger({ raw: true });
228+
logger.logTable([{ foo: 1, bar: 2 }]);
229+
230+
expect(logger.log).not.toHaveBeenCalled();
231+
});
232+
233+
it('does not log anything if value is not an array', () => {
234+
const logger = createLogger();
235+
logger.logTable({});
236+
logger.logTable(null);
237+
logger.logTable(0);
238+
logger.logTable('');
239+
240+
expect(logger.log).not.toHaveBeenCalled();
241+
});
242+
243+
it('does not log anything if array is empy', () => {
244+
const logger = createLogger();
245+
logger.logTable([]);
246+
247+
expect(logger.log).not.toHaveBeenCalled();
248+
});
249+
250+
it('does not log anything if array items have no properties', () => {
251+
const logger = createLogger();
252+
logger.logTable([{}]);
253+
254+
expect(logger.log).not.toHaveBeenCalled();
255+
});
256+
257+
it('logs a header for each item\'s properties', () => {
258+
const logger = createLogger();
259+
logger.logTable([{ foo: 1, bar: 2 }]);
260+
261+
expect(logger.log).toHaveBeenCalledWith(
262+
chalk.reset('-->') + ' ',
263+
chalk.reset('│ foo │ bar │') + '\n',
264+
);
265+
});
266+
267+
it('logs padded headers according to longest column\'s value', () => {
268+
const logger = createLogger();
269+
logger.logTable([{ a: 'foo', b: 'barbaz' }]);
270+
271+
expect(logger.log).toHaveBeenCalledWith(
272+
chalk.reset('-->') + ' ',
273+
chalk.reset('│ a │ b │') + '\n',
274+
);
275+
});
276+
277+
it('logs each items\'s values', () => {
278+
const logger = createLogger();
279+
logger.logTable([{ foo: 123 }, { foo: 456 }]);
280+
281+
expect(logger.log).toHaveBeenCalledWith(
282+
chalk.reset('-->') + ' ',
283+
chalk.reset('│ 123 │') + '\n',
284+
);
285+
expect(logger.log).toHaveBeenCalledWith(
286+
chalk.reset('-->') + ' ',
287+
chalk.reset('│ 456 │') + '\n',
288+
);
289+
});
290+
291+
it('logs each items\'s values padded according to longest column\'s value', () => {
292+
const logger = createLogger();
293+
logger.logTable([{ foo: 1 }, { foo: 123 }]);
294+
295+
expect(logger.log).toHaveBeenCalledWith(
296+
chalk.reset('-->') + ' ',
297+
chalk.reset('│ 1 │') + '\n',
298+
);
299+
});
300+
301+
it('logs items with different properties in each', () => {
302+
const logger = createLogger();
303+
logger.logTable([{ foo: 1 }, { bar: 2 }]);
304+
305+
expect(logger.log).toHaveBeenCalledWith(
306+
chalk.reset('-->') + ' ',
307+
chalk.reset('│ foo │ bar │') + '\n',
308+
);
309+
expect(logger.log).toHaveBeenCalledWith(
310+
chalk.reset('-->') + ' ',
311+
chalk.reset('│ 1 │ │') + '\n',
312+
);
313+
expect(logger.log).toHaveBeenCalledWith(
314+
chalk.reset('-->') + ' ',
315+
chalk.reset('│ │ 2 │') + '\n',
316+
);
317+
});
318+
});

0 commit comments

Comments
 (0)