Skip to content

Commit

Permalink
Add a prompt before you submit a crash report. (#749)
Browse files Browse the repository at this point in the history
  • Loading branch information
tikurahul authored and rwaldron committed Jun 13, 2016
1 parent 16cee4a commit 43d1cc5
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 68 deletions.
42 changes: 2 additions & 40 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ var util = require('util');
// Third Party Dependencies
var async = require('async');
var colors = require('colors');
var inquirer = require('inquirer');
var semver = require('semver');

// Internal
var discover = require('./discover');
var logs = require('./logs');
var Menu = require('./menu');
var updates = require('./update-fetch');
var provision = require('./tessel/provision');
var Tessel = require('./tessel/tessel');
Expand Down Expand Up @@ -216,7 +216,7 @@ Tessel.get = function(opts) {
var map = {};

// Open up an interactive menu for the user to choose
return controller.menu({
return Menu.prompt({
prefix: colors.grey('INFO '),
prompt: {
name: 'selected',
Expand Down Expand Up @@ -978,44 +978,6 @@ controller.tesselEnvVersions = opts => {
});
};

/*
controller.menu({
// Custom prefix
prefix: colors.grey('INFO '),
prompt: [inquirer.prompt options],
// Custom answer -> data translation
translate: function(answer) {
// answer =>
// { [prompt.name]: ... }
return answer[prompt.name];
}
}) => Promise
*/

controller.menu = function(setup) {
var options = setup.prompt;

if (options.type === 'list') {
options.choices.push('\tExit');
}

// Enforce a customized prompt prefix
inquirer.prompt.prompts[options.type].prototype.prefix = function(str) {
// String() used to coerce an `undefined` to ''. Do not change.
return String(setup.prefix) + str;
};

return new Promise(function(resolve) {
inquirer.prompt([options], function(answer) {
if (setup.translate) {
resolve(setup.translate(answer));
} else {
resolve(answer);
}
});
});
};

module.exports = controller;

// Shared exports
Expand Down
85 changes: 64 additions & 21 deletions lib/crash-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ var tags = require('common-tags');

// Internal
var logs = require('./logs');
var Menu = require('./menu');
var packageJson = require('../package.json');
var Preferences = require('./preferences');

// the value of the crash reporter preference
// the value has to be one of 'on' or 'off'
var CRASH_REPORTER_PREFERENCE = 'crash.reporter.preference';
var CRASH_REPORTER_PROMPT = 'crash.reporter.prompt';
var CRASH_PROMPT_MESSAGE = `\nSubmit Crash Report to help Tessel Developers improve the CLI ?
If yes(y), subsequent crashes will be submitted automatically.`;

var CRASH_REPORTER_BASE_URL = 'http://crash-reporter.tessel.io';

// override for testing
Expand Down Expand Up @@ -64,6 +69,37 @@ CrashReporter.sanitize.redactions = [
(stack) => stack.replace(new RegExp(escape(os.homedir()), 'g'), ''),
];

CrashReporter.prompt = function() {
return new Promise((resolve) => {
Preferences.read(CRASH_REPORTER_PROMPT, 'true')
.then(value => {
if (value === 'false') {
// not a first time crash
resolve(true);
} else {
// prompt for info
var prompt = Menu.prompt({
prompt: {
name: 'selected',
type: 'confirm',
message: CRASH_PROMPT_MESSAGE
}
});
prompt.then(selection => {
var selected = selection['selected'];
if (selected === false) {
resolve(false);
} else {
return Preferences.write(CRASH_REPORTER_PROMPT, 'false')
.then(() => {
resolve(true);
});
}
});
}
});
});
};

CrashReporter.submit = function(report, opts) {
if (opts === undefined) {
Expand All @@ -72,30 +108,37 @@ CrashReporter.submit = function(report, opts) {

const silent = (opts.silent || false);

return Preferences.read(CRASH_REPORTER_PREFERENCE, 'on')
.then(value => {
if (value === 'on') {
var labels = tags.stripIndent `
${packageJson.name},
CLI version: ${packageJson.version},
Node version: ${process.version},
OS platform: ${os.platform()},
OS release: ${os.release()}
`;

var stack = CrashReporter.sanitize(report.stack || String(report));

return CrashReporter.post(labels, stack)
.then(fingerprint => {
if (!silent) {
logs.info(`Crash Reported: ${CRASH_REPORTER_BASE_URL}/crashes?fingerprint=${fingerprint}`);
return CrashReporter.prompt()
.then(success => {
if (success) {
return Preferences.read(CRASH_REPORTER_PREFERENCE, 'on')
.then(value => {
if (value === 'on') {
var labels = tags.stripIndent `
${packageJson.name},
CLI version: ${packageJson.version},
Node version: ${process.version},
OS platform: ${os.platform()},
OS release: ${os.release()}
`;

var stack = CrashReporter.sanitize(report.stack || String(report));

return CrashReporter.post(labels, stack)
.then(fingerprint => {
if (!silent) {
logs.info(`Crash Reported: ${CRASH_REPORTER_BASE_URL}/crashes?fingerprint=${fingerprint}`);
}
});
}
}).catch(error => {
// do nothing
// do not crash the crash reporter :)
logs.err('Error submitting crash report', error, error.stack);
});
} else {
logs.info('Did not submit crash report.');
}
}).catch(error => {
// do nothing
// do not crash the crash reporter :)
logs.err('Error submitting crash report', error);
});
};

Expand Down
44 changes: 44 additions & 0 deletions lib/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Third Party Dependencies
var inquirer = require('inquirer');

/*
controller.menu({
// Custom prefix
prefix: colors.grey('INFO '),
prompt: [inquirer.prompt options],
// Custom answer -> data translation
translate: function(answer) {
// answer =>
// { [prompt.name]: ... }
return answer[prompt.name];
}
}) => Promise
*/

var Menu = {};

Menu.prompt = (setup) => {
var options = setup.prompt;

if (options.type === 'list') {
options.choices.push('\tExit');
}

// Enforce a customized prompt prefix
inquirer.prompt.prompts[options.type].prototype.prefix = (str) => {
// String() used to coerce an `undefined` to ''. Do not change.
return String(setup.prefix) + str;
};

return new Promise(function(resolve) {
inquirer.prompt([options], function(answer) {
if (setup.translate) {
resolve(setup.translate(answer));
} else {
resolve(answer);
}
});
});
};

module.exports = Menu;
1 change: 1 addition & 0 deletions test/common/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ global.RSA = require('../../lib/tessel/rsa-delegation');

// ./lib/*
global.CrashReporter = require('../../lib/crash-reporter');
global.Menu = require('../../lib/menu');
global.Preferences = require('../../lib/preferences');
global.controller = require('../../lib/controller');
global.discover = require('../../lib/discover');
Expand Down
8 changes: 6 additions & 2 deletions test/unit/crash-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

exports['CrashReporter'] = {
surface: function(test) {
test.expect(5);
test.expect(6);
test.equal(typeof CrashReporter.on, 'function');
test.equal(typeof CrashReporter.off, 'function');
test.equal(typeof CrashReporter.post, 'function');
test.equal(typeof CrashReporter.submit, 'function');
test.equal(typeof CrashReporter.prompt, 'function');
test.equal(typeof CrashReporter.test, 'function');
test.done();
},
Expand Down Expand Up @@ -118,6 +119,7 @@ exports['CrashReporter.submit'] = {
this.logsInfo = this.sandbox.stub(logs, 'info');
this.pRead = this.sandbox.stub(Preferences, 'read').returns(Promise.resolve('on'));
this.pWrite = this.sandbox.stub(Preferences, 'write').returns(Promise.resolve());
this.crPrompt = this.sandbox.stub(CrashReporter, 'prompt').returns(Promise.resolve(true));
this.crPost = this.sandbox.spy(CrashReporter, 'post');
this.crSanitize = this.sandbox.spy(CrashReporter, 'sanitize');
this.request = this.sandbox.stub(request, 'post', (opts, handler) => {
Expand All @@ -132,11 +134,12 @@ exports['CrashReporter.submit'] = {
},

submit: function(test) {
test.expect(7);
test.expect(8);

var report = 'Error: undefined is not a function';
CrashReporter.submit(report).then(() => {
var args = this.crPost.lastCall.args;
test.equal(this.crPrompt.callCount, 1);
test.equal(this.crPost.callCount, 1);
test.equal(typeof args[0], 'string');
test.ok(args[0].includes('CLI version'), 'Label CLI version should be present');
Expand Down Expand Up @@ -210,6 +213,7 @@ exports['CrashReporter.sanitize'] = {
this.logsInfo = this.sandbox.stub(logs, 'info');
this.pRead = this.sandbox.stub(Preferences, 'read').returns(Promise.resolve('on'));
this.pWrite = this.sandbox.stub(Preferences, 'write').returns(Promise.resolve());
this.crPrompt = this.sandbox.stub(CrashReporter, 'prompt').returns(Promise.resolve(true));
this.crPost = this.sandbox.spy(CrashReporter, 'post');
this.request = this.sandbox.stub(request, 'post', (opts, handler) => {
return handler(null, {}, '{}');
Expand Down
7 changes: 4 additions & 3 deletions test/unit/menu.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Test dependencies are required and exposed in common/bootstrap.js
/*global Menu */

exports['controller.menu'] = {
exports['Menu.prompt'] = {
setUp: function(done) {
this.sandbox = sinon.sandbox.create();
this.logsWarn = this.sandbox.stub(logs, 'warn', function() {});
Expand Down Expand Up @@ -35,7 +36,7 @@ exports['controller.menu'] = {
});
});

controller.menu({
Menu.prompt({
prompt: {
name: 'selected',
type: 'list',
Expand Down Expand Up @@ -78,7 +79,7 @@ exports['controller.menu'] = {
});
});

controller.menu({
Menu.prompt({
prompt: {
name: 'selected',
type: 'list',
Expand Down
5 changes: 3 additions & 2 deletions test/unit/tessel.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test dependencies are required and exposed in common/bootstrap.js
/*global Menu */

exports['Tessel (get)'] = {

Expand All @@ -23,7 +24,7 @@ exports['Tessel (get)'] = {
this.logsWarn = this.sandbox.stub(logs, 'warn', function() {});
this.logsInfo = this.sandbox.stub(logs, 'info', function() {});

this.menu = this.sandbox.stub(controller, 'menu', function() {
this.menu = this.sandbox.stub(Menu, 'prompt', function() {
return Promise.resolve();
});

Expand Down Expand Up @@ -539,7 +540,7 @@ exports['Tessel (get); filter: unauthorized'] = {
this.logsWarn = this.sandbox.stub(logs, 'warn', function() {});
this.logsInfo = this.sandbox.stub(logs, 'info', function() {});

this.menu = this.sandbox.stub(controller, 'menu', function() {
this.menu = this.sandbox.stub(Menu, 'prompt', function() {
return Promise.resolve();
});

Expand Down

0 comments on commit 43d1cc5

Please sign in to comment.