From 6d5b16ad0ff0889b90f52f95119c03b308b51688 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 2 Aug 2015 16:13:25 -0400 Subject: [PATCH 01/30] INT-459: use newrelic agent see README.md for config info --- .gitignore | 1 + README.md | 24 ++++++++++++++++++++++++ package.json | 2 ++ src/electron/config.js | 33 +++++++++++++++++++++++++++++++++ src/electron/index.js | 2 ++ src/electron/newrelic.js | 30 ++++++++++++++++++++++++++++++ 6 files changed, 92 insertions(+) create mode 100644 src/electron/config.js create mode 100644 src/electron/newrelic.js diff --git a/.gitignore b/.gitignore index cb923a46d0e..e12ded0f96a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules .DS_Store .idea/ *.iml +config.json diff --git a/README.md b/README.md index d283284bf8e..c400de716be 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,30 @@ To compile electron + the app and the installer for your current platform: npm run release ``` +## Configuration + +### Newrelic + +By default, newrelic is disabled because we don't want to check-in the license key. + +Login to newrelic and copy the [license_key](https://rpm.newrelic.com/accounts/933509). + +It can be set at build time by adding the following to `./config.json`: + +```json +{ + "newrelic": { + "license_key": "" + } +} +``` + +Alternatively to just try it out or to debug a problem use environment variables: + +```bash +newrelic__license_key='' npm start; +``` + [setup-osx]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#osx-setup [setup-windows]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#windows-setup [setup-linux]: https://github.com/mongodb-js/mongodb-js/blob/master/docs/setup.md#linux-setup diff --git a/package.json b/package.json index 0e6dd42bce0..928ba8fc9b0 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,8 @@ "mongodb-language-model": "^0.2.1", "mongodb-schema": "^3.2.1", "mousetrap": "^1.5.3", + "nconf": "^0.7.1", + "newrelic": "^1.21.1", "numeral": "^1.5.3", "octicons": "https://github.com/github/octicons/archive/v2.2.0.tar.gz", "phantomjs-polyfill": "0.0.1", diff --git a/src/electron/config.js b/src/electron/config.js new file mode 100644 index 00000000000..c824d4cd29f --- /dev/null +++ b/src/electron/config.js @@ -0,0 +1,33 @@ +var app = require('app'); +var nconf = require('nconf'); +var path = require('path'); +var pkg = require('../../package.json'); + +var CONFIG_FILE = path.resolve(__dirname, '../config.json'); + +var defaults = { + newrelic: { + license_key: null, + app_name: pkg.product_name, + use_ssl: true, + log_enabled: true, + log_level: 'info', + log_filepath: path.join(app.getPath('temp'), 'newrelic.log') + } +}; + +nconf + .file(CONFIG_FILE) + .env('__') + .argv() + .use('memory') + .defaults(defaults); + +// Some versions of node leave the colon on the end. +nconf.overrides({ + newrelic: { + enabled: !!nconf.get('newrelic:license_key') + } +}); + +module.exports = nconf; diff --git a/src/electron/index.js b/src/electron/index.js index 586c4fbda28..542a17622d3 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -1,3 +1,5 @@ +require('./newrelic'); + if (process.env.NODE_ENV === 'development') { require('./livereload'); } diff --git a/src/electron/newrelic.js b/src/electron/newrelic.js new file mode 100644 index 00000000000..9764bbe7391 --- /dev/null +++ b/src/electron/newrelic.js @@ -0,0 +1,30 @@ +/** + * Sets environment variables to configure the newrelic agent + * and require the newrelic module to start the agent. + */ +var _ = require('lodash'); +var config = require('./config'); +var debug = require('debug')('scout:electron:newrelic'); + +var ENV = { + NEW_RELIC_ENABLED: config.get('newrelic:enabled'), + NEW_RELIC_APP_NAME: config.get('newrelic:app_name'), + NEW_RELIC_LICENSE_KEY: config.get('newrelic:license_key'), + NEW_RELIC_USE_SSL: config.get('newrelic:use_ssl'), + NEW_RELIC_LOG_ENABLED: config.get('newrelic:log_enabled'), + NEW_RELIC_LOG_LEVEL: config.get('newrelic:log_level'), + NEW_RELIC_LOG: config.get('newrelic:log_filepath'), + NEW_RELIC_SLOW_SQL_ENABLED: false, + NEW_RELIC_UTILIZATION_DETECT_AWS: false, + NEW_RELIC_UTILIZATION_DETECT_DOCKER: false +}; + +_.assign(process.env, ENV); + +if (config.get('newrelic:enabled')) { + debug('newrelic enabled! view log file at `%s`', config.get('newrelic:log_filepath')); +} else { + debug('newrelic not enabled'); +} + +require('newrelic'); From 9a66524975a197490afecf7cbdb1f8d64abe58a8 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 8 Jul 2015 13:05:13 -0400 Subject: [PATCH 02/30] INT-175: Initial intercom setup --- src/app.js | 5 +++++ src/index.jade | 2 +- src/intercom.js | 32 ++++++++++++++++++++++++++++++++ src/livereload.js | 11 +++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/intercom.js create mode 100644 src/livereload.js diff --git a/src/app.js b/src/app.js index cc17b9049a3..19b3b56cc3b 100644 --- a/src/app.js +++ b/src/app.js @@ -14,6 +14,7 @@ var getOrCreateClient = require('scout-client'); var ViewSwitcher = require('ampersand-view-switcher'); var View = require('ampersand-view'); var localLinks = require('local-links'); +var intercom = require('./intercom'); /** * The top-level application singleton that brings everything together! @@ -78,9 +79,12 @@ var Application = View.extend({ _onDOMReady: function() { this.el = document.querySelector('#application'); this.render(); + intercom.inject(); this.listenTo(this.router, 'page', this.onPageChange); + this.router.on('page', intercom.update); + this.router.history.start({ pushState: false, root: '/' @@ -126,6 +130,7 @@ var Application = View.extend({ event.preventDefault(); this.router.history.navigate(pathname); } + intercom.update(); } }); diff --git a/src/index.jade b/src/index.jade index c403c816e96..980a1144742 100644 --- a/src/index.jade +++ b/src/index.jade @@ -2,7 +2,7 @@ doctype html html(lang='en') head title MongoDB - meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'self' http://localhost:35729; style-src 'self' 'unsafe-inline';") + meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'self' http://localhost:35729 https://widget.intercom.io https://js.intercomcdn.com/; style-src 'self' 'unsafe-inline';") meta(name='viewport', content='initial-scale=1') link(rel='stylesheet', href='index.css', charset='UTF-8') diff --git a/src/intercom.js b/src/intercom.js new file mode 100644 index 00000000000..b42c8f16c2a --- /dev/null +++ b/src/intercom.js @@ -0,0 +1,32 @@ +/*eslint new-cap:0*/ +/** + * App state has been updated so notfiy intercom of it. + */ +module.exports.update = function() { + window.Intercom('update'); +}; + +/** + * Injects the intercom client script. + */ +module.exports.inject = function() { + var i = function() { + i.c(arguments); + }; + i.q = []; + i.c = function(args) { + i.q.push(args); + }; + window.Intercom = i; + + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = 'https://widget.intercom.io/widget/p57suhg7'; + head.appendChild(script); + + window.Intercom('boot', { + app_id: 'p57suhg7' + // @todo (imlucas): Include name, email, and first_run_at in this hash. + }); +}; diff --git a/src/livereload.js b/src/livereload.js new file mode 100644 index 00000000000..77513363c07 --- /dev/null +++ b/src/livereload.js @@ -0,0 +1,11 @@ +/** + * Inject the livereload client so windows are reloaded automatically + * when any client assets/code are changed. + */ +module.exports.inject = function() { + var head = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = 'http://localhost:35729/livereload.js'; + head.appendChild(script); +}; From e69fab8270b596452dcfc39caa4e1c1f08e2887b Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 8 Jul 2015 18:45:42 -0400 Subject: [PATCH 03/30] feature(models): First crack at User --- package.json | 1 + src/models/user.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/models/user.js diff --git a/package.json b/package.json index 928ba8fc9b0..ad50f1f3809 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "scout-client": "http://bin.mongodb.org/js/scout-client/v0.0.5/scout-client-0.0.5.tar.gz", "scout-server": "http://bin.mongodb.org/js/scout-server/v0.0.4/scout-server-0.0.4.tar.gz", "tiny-lr": "^0.1.6", + "uuid": "^2.0.1", "watch": "^0.16.0" }, "devDependencies": { diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 00000000000..afd54ae74bf --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,44 @@ +var Model = require('ampersand-model'); +var localforage = require('ampersand-sync-localforage'); +var uuid = require('uuid'); + +var User = Model.extend({ + modelType: 'User', + props: { + id: 'string', + name: 'string', + email: 'string', + created_at: 'date' + }, + sync: localforage('User') +}); + +User.getOrCreate = function(done) { + var id = localStorage.getItem('user_id'); + var user; + + if (!id) { + id = uuid.v4(); + localStorage.setItem('user_id', id); + user = new User({ + id: id, + created_at: new Date() + }); + user.save(); + done(null, user); + } else { + user = new User({ + id: id + }); + user.fetch({ + success: function() { + done(null, user); + }, + error: function(model, err) { + done(err); + } + }); + } +}; + +module.exports = User; From a9c52045756ace40acc12b15a7d71332befccbe2 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 8 Jul 2015 18:46:33 -0400 Subject: [PATCH 04/30] feature(app): wire in user model and add some intercom event tracking --- src/app.js | 10 +++++++++- src/home/index.js | 4 ++++ src/home/index.less | 6 ++++++ src/intercom.js | 39 +++++++++++++++++++++++++++------------ src/router.js | 4 +++- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/app.js b/src/app.js index 19b3b56cc3b..704f00919d0 100644 --- a/src/app.js +++ b/src/app.js @@ -15,7 +15,7 @@ var ViewSwitcher = require('ampersand-view-switcher'); var View = require('ampersand-view'); var localLinks = require('local-links'); var intercom = require('./intercom'); - +var User = require('./models/user'); /** * The top-level application singleton that brings everything together! * @@ -85,6 +85,14 @@ var Application = View.extend({ this.router.on('page', intercom.update); + /*eslint no-console:0*/ + User.getOrCreate(function(err, user) { + if (err) return console.error(err); + + this.user.set(user.serialize()); + intercom.inject(user); + }.bind(this)); + this.router.history.start({ pushState: false, root: '/' diff --git a/src/home/index.js b/src/home/index.js index fbcf275f6c9..f0a29b57886 100644 --- a/src/home/index.js +++ b/src/home/index.js @@ -3,6 +3,7 @@ var format = require('util').format; var SidebarView = require('../sidebar'); var debug = require('debug')('scout-ui:home'); var CollectionView = require('./collection'); +var intercom = require('../intercom'); var app = require('ampersand-app'); var HomeView = View.extend({ @@ -46,9 +47,12 @@ var HomeView = View.extend({ this.ns = model.getId(); this.updateTitle(model); + app.navigate(format('schema/%s', model.getId()), { silent: true }); + + intercom.track('View Schema'); }, template: require('./index.jade'), subviews: { diff --git a/src/home/index.less b/src/home/index.less index aa9e9f9c967..6ecd89e0f2f 100644 --- a/src/home/index.less +++ b/src/home/index.less @@ -1,3 +1,9 @@ +.intercom-messenger-active { + #application { + margin-right: 370px; + } +} + .page { max-height: 100%; min-height: 100%; diff --git a/src/intercom.js b/src/intercom.js index b42c8f16c2a..8be2dc9cd23 100644 --- a/src/intercom.js +++ b/src/intercom.js @@ -1,3 +1,15 @@ +var _ = require('lodash'); +var pkg = require('../package.json'); + +var i = function() { + i.c(arguments); +}; +i.q = []; +i.c = function(args) { + i.q.push(args); +}; +window.Intercom = i; + /*eslint new-cap:0*/ /** * App state has been updated so notfiy intercom of it. @@ -6,27 +18,30 @@ module.exports.update = function() { window.Intercom('update'); }; +// @todo (imlucas): use http://npm.im/osx-release and include platform details +// in event tracking. +// @todo (imlucas): Expose to main renderer via IPC so the server can track +// whatever events it needs to as well. +module.exports.track = function(eventName, data) { + data = _.extend(data || {}, { + app_version: pkg.version + }); + window.Intercom('trackEvent', eventName, data); +}; + /** * Injects the intercom client script. */ -module.exports.inject = function() { - var i = function() { - i.c(arguments); - }; - i.q = []; - i.c = function(args) { - i.q.push(args); - }; - window.Intercom = i; - +module.exports.inject = function(user) { var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'https://widget.intercom.io/widget/p57suhg7'; head.appendChild(script); - window.Intercom('boot', { + var config = _.extend(user.toJSON(), { app_id: 'p57suhg7' - // @todo (imlucas): Include name, email, and first_run_at in this hash. }); + config.user_id = user.id; + window.Intercom('boot', config); }; diff --git a/src/router.js b/src/router.js index 03555658931..396c8571bbf 100644 --- a/src/router.js +++ b/src/router.js @@ -1,5 +1,5 @@ var AmpersandRouter = require('ampersand-router'); - +var intercom = require('./intercom'); var HomePage = require('./home'); var Connect = require('./connect'); @@ -12,6 +12,7 @@ module.exports = AmpersandRouter.extend({ '(*path)': 'catchAll' }, index: function() { + intercom.track('Connected to MongoDB'); this.trigger('page', new HomePage({})); }, schema: function(ns) { @@ -23,6 +24,7 @@ module.exports = AmpersandRouter.extend({ this.redirectTo(''); }, connect: function() { + intercom.track('App Launched'); this.trigger('page', new Connect({})); } }); From 1fa92206d8911c53d31f36d15b1f7f0c54498096 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:49:02 -0400 Subject: [PATCH 05/30] add mongodb-leaf images used for setup --- images/mongodb-leaf-development.png | Bin 0 -> 8323 bytes images/mongodb-leaf.png | Bin 0 -> 5730 bytes images/mongodb-leaf_256x256.png | Bin 0 -> 6247 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/mongodb-leaf-development.png create mode 100644 images/mongodb-leaf.png create mode 100644 images/mongodb-leaf_256x256.png diff --git a/images/mongodb-leaf-development.png b/images/mongodb-leaf-development.png new file mode 100644 index 0000000000000000000000000000000000000000..4eef572b54fc0d6e0054f3e5a71d82b8189d0ece GIT binary patch literal 8323 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 BDK7v3 literal 0 HcmV?d00001 diff --git a/images/mongodb-leaf.png b/images/mongodb-leaf.png new file mode 100644 index 0000000000000000000000000000000000000000..d177739dfbd0cae31733e2aa1ded343b0fabe2ed GIT binary patch literal 5730 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 literal 0 HcmV?d00001 diff --git a/images/mongodb-leaf_256x256.png b/images/mongodb-leaf_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..f55c910fea8b54b5bbd15d816f00c5eaea778564 GIT binary patch literal 6247 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 literal 0 HcmV?d00001 From 08014a7196fee33012764fece96f904cbdb7b235 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:52:58 -0400 Subject: [PATCH 06/30] Make github's ipc available to app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/atom/electron/blob/master/docs/api/ipc-renderer.md Have to patch in for now bc we’re still using browserify. --- src/app.js | 35 ++++++++++++++++++++++++++++------- src/index.jade | 6 ++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/app.js b/src/app.js index 704f00919d0..849cd49e50c 100644 --- a/src/app.js +++ b/src/app.js @@ -15,7 +15,8 @@ var ViewSwitcher = require('ampersand-view-switcher'); var View = require('ampersand-view'); var localLinks = require('local-links'); var intercom = require('./intercom'); -var User = require('./models/user'); +var debug = require('debug')('scout:app'); + /** * The top-level application singleton that brings everything together! * @@ -79,14 +80,13 @@ var Application = View.extend({ _onDOMReady: function() { this.el = document.querySelector('#application'); this.render(); - intercom.inject(); this.listenTo(this.router, 'page', this.onPageChange); this.router.on('page', intercom.update); /*eslint no-console:0*/ - User.getOrCreate(function(err, user) { + app.getOrCreateUser(function(err, user) { if (err) return console.error(err); this.user.set(user.serialize()); @@ -151,11 +151,15 @@ var state = new Application({ var QueryOptions = require('./models/query-options'); var Connection = require('./models/connection'); var MongoDBInstance = require('./models/mongodb-instance'); +var User = require('./models/user'); var Router = require('./router'); var Statusbar = require('./statusbar'); app.extend({ client: null, + meta: { + 'App Version': pkg.version + }, init: function() { state.statusbar = new Statusbar(); this.connection = new Connection(); @@ -169,8 +173,21 @@ app.extend({ }; state.router = new Router(); + + this.on('change:ipc', function() { + debug('ipc now available!'); + }); + this.router = state.router; + }, + use: function(fn) { + fn.call(null, this); }, - navigate: state.navigate.bind(state) + intercom: intercom, + navigate: state.navigate.bind(state), + back: function() { + this.router.history.history.back(); + }, + getOrCreateUser: User.getOrCreate }); Object.defineProperty(app, 'statusbar', { @@ -179,6 +196,12 @@ Object.defineProperty(app, 'statusbar', { } }); +Object.defineProperty(app, 'user', { + get: function() { + return state.user; + } +}); + Object.defineProperty(app, 'client', { get: function() { return getOrCreateClient({ @@ -188,13 +211,11 @@ Object.defineProperty(app, 'client', { }); app.init(); -// expose app globally for debugging purposes -window.app = app; - function render_app() { state._onDOMReady(); } domReady(render_app); +// expose app globally for debugging purposes window.app = app; diff --git a/src/index.jade b/src/index.jade index 980a1144742..c722848ddb8 100644 --- a/src/index.jade +++ b/src/index.jade @@ -18,3 +18,9 @@ html(lang='en') script(src='src/electron/development-debug-header.js', charset='UTF-8') script(src='index.js', charset='UTF-8') + script(type='text/javascript'). + //- Make electron ipc available via `app`. + window.app.use(function(app){ + app.ipc = require('ipc'); + app.trigger('change:ipc'); + }); From b129f142915b4529c50179f4da592bbbe82c49fa Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:53:55 -0400 Subject: [PATCH 07/30] Cleanup some autoupdate messages Lays groundwork for INT-501 --- src/electron/auto-updater.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/electron/auto-updater.js b/src/electron/auto-updater.js index e57822f0ff0..6e53a786176 100644 --- a/src/electron/auto-updater.js +++ b/src/electron/auto-updater.js @@ -1,12 +1,12 @@ var app = require('app'); +var ipc = require('ipc'); var updater = module.exports = require('auto-updater'); -var debug = require('debug')('scout-electron:auto-updater'); +var debug = require('debug')('scout:electron:auto-updater'); var FEED_URL = 'http://squirrel.mongodb.parts/scout/releases/latest?version=' + app.getVersion(); debug('Using feed url', FEED_URL); - updater.on('checking-for-update', function() { - debug('checking for update', arguments); + debug('checking for update'); }); updater.on('error', function(err) { @@ -14,20 +14,36 @@ updater.on('error', function(err) { }); updater.on('update-available', function() { - debug('update available', arguments); + debug('update available and downloading...'); }); updater.on('update-not-available', function() { - debug('No update available', arguments); + debug('No update available'); +}); + +updater.on('update-downloaded', function(evt, releaseNotes, releaseName, releaseDate, updateUrl) { + debug('Update downloaded!'); + ipc.send('update-downloaded', { + notes: releaseNotes, + name: releaseName, + date: releaseDate, + url: updateUrl + }); }); -updater.on('update-downloaded', function() { - debug('Update downloaded', arguments); +// When the UI gets the `update-downloaded` message, +// it will show the "Restart to upgrade!" button which +// when clicked, sends the `install-update` message that +// we handle below to actually quit the app and install +// the update. +ipc.on('install-update', function() { + debug('quitting app and updating...'); + app.quitAndUpdate(); }); updater.setFeedUrl(FEED_URL); -app.on('ready', function() { +app.on('will-finish-launching', function() { debug('checking for updates...'); updater.checkForUpdates(); }); From e4ef48e01232b8515b05cf994cdd52d69327436c Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:54:09 -0400 Subject: [PATCH 08/30] Crash reporter cleanup --- src/electron/crash-reporter.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/electron/crash-reporter.js b/src/electron/crash-reporter.js index fa884d112c3..ba1cfbc457b 100644 --- a/src/electron/crash-reporter.js +++ b/src/electron/crash-reporter.js @@ -1,8 +1,12 @@ +var app = require('app'); var reporter = module.exports = require('crash-reporter'); -reporter.start({ - productName: 'Scout', - companyName: 'MongoDB', - submitUrl: 'http://breakpad.mongodb.parts/post', - autoSubmit: true +// @todo (imlucas): Point at flytrap. +app.on('will-finish-launching', function() { + reporter.start({ + productName: 'Scout', + companyName: 'MongoDB', + submitUrl: 'http://breakpad.mongodb.parts/post', + autoSubmit: true + }); }); From 31bf979b54c1e8a9d2862c7737157334415eda06 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:57:00 -0400 Subject: [PATCH 09/30] github oauth bindings, windowing cleanup + setup persistence --- src/electron/github-oauth-flow.js | 96 +++++++++++++++++++++++++++++++ src/electron/index.js | 58 ++++++++++++++----- src/electron/setup.js | 41 +++++++++++++ src/electron/window-manager.js | 71 ++++++++++++++--------- 4 files changed, 225 insertions(+), 41 deletions(-) create mode 100644 src/electron/github-oauth-flow.js create mode 100644 src/electron/setup.js diff --git a/src/electron/github-oauth-flow.js b/src/electron/github-oauth-flow.js new file mode 100644 index 00000000000..79a882c3a40 --- /dev/null +++ b/src/electron/github-oauth-flow.js @@ -0,0 +1,96 @@ +var parseURL = require('url').parse; +var request = require('request'); +var octonode = require('octonode'); +var createWindow = require('./window-manager').create; +var format = require('util').format; +var debug = require('debug')('scout:electron:github-oauth-flow'); +var client; + +function exchangeCodeForToken(opts, code, fn) { + var url = 'https://github.com/login/oauth/access_token' + + '?client_id=' + opts.github_client_id + + '&client_secret=' + opts.github_client_secret + + '&code=' + code; + + request.get({ + url: url, + json: true + }, function(err, res, body) { + debug('exchange result res', res); + if (err) { + err.body = body; + err.res = res; + return fn(err); + } + fn(null, body); + }); +} + +function getUser(access_token, done) { + client = octonode.client(access_token); + client.get('/user', {}, function(err, status, body) { + if (err) return done(err); + var res = { + avatar_url: body.avatar_url, + name: body.name, + company_name: body.company, + location: body.location, + email: body.email, + github_username: body.login, + github_score: body.public_repos + body.public_gists + body.followers + body.following, + github_last_activity_at: body.updated_at + }; + + debug('loaded github user data', res); + done(null, res); + }); +} + +function oauthCallback(opts, url, done) { + var query = parseURL(url, true).query; + debug('oauth callback response', query); + if (query.error) { + return done(new Error('GitHub auth failed: ' + JSON.stringify(query))); + } + exchangeCodeForToken(opts, query.code, function(err, res) { + if (err) return done(err); + + getUser(res.access_token, function(err, data) { + if (err) return done(err); + + data.github_access_token = res.access_token; + done(null, data); + }); + }); +} + +module.exports = function(opts, done) { + var url = format('https://github.com/login/oauth/authorize?client_id=%s&scope=%s', + opts.github_client_id, opts.github_scope); + + var _window = createWindow({ + url: url, + centered: true, + 'always-on-top': true, + 'use-content-size': true, + 'node-integration': false // Important or form inputs won't work. + }); + debug('starting github oauth web flow...'); + + var onClose = function() { + done(new Error('GitHub oAuth flow cancelled!')); + }; + // Handle the user starting the github oauth flow but at + // any time they could chose to close the window and cancel. + _window.on('close', onClose); + + _window.webContents.on('did-get-redirect-request', function(event, fromURL, toURL) { + debug('github redirected authWindow %s -> %s', event, fromURL, toURL); + oauthCallback(opts, toURL, function(err, res) { + _window.off('close', onClose); + _window.close(); + _window = null; + done(err, res); + }); + }); +}; diff --git a/src/electron/index.js b/src/electron/index.js index 542a17622d3..989e3ef7dcc 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -5,27 +5,57 @@ if (process.env.NODE_ENV === 'development') { } var app = require('app'); -var debug = require('debug')('scout-electron'); -var mongo = require('./mongo'); +var BrowserWindow = require('browser-window'); +var ipc = require('ipc'); +var windows = require('./window-manager'); +var githubOauthFlow = require('./github-oauth-flow'); +var setup = require('./setup'); +var debug = require('debug')('scout:electron'); + +var settings; +try { + settings = require('../../settings.json'); +} catch (e) { + debug('no settings file found. external services disabled.'); + settings = {}; +} app.on('window-all-closed', function() { debug('All windows closed. Quitting app.'); app.quit(); }); -mongo.start(function() { - debug('mongo started!'); +app.on('window-all-closed', function() { + app.quit(); }); -app.on('before-quit', function() { - mongo.stop(function() { - debug('mongo stopped'); - }); +app.on('open-setup-dialog', function(opts) { + windows.openSetupDialog(opts); +}); + +app.on('open-connect-dialog', function(opts) { + windows.openConnectDialog(opts); }); -module.exports = { - autoupdater: require('./auto-updater'), - crashreporter: require('./crash-reporter'), - windows: require('./window-manager'), - menu: require('./menu') -}; +app.on('ready', function() { + ipc.on('open-setup-dialog', app.emit.bind(app, 'open-setup-dialog')); + ipc.on('open-connect-dialog', app.emit.bind(app, 'open-connect-dialog')); + ipc.on('open-github-oauth-flow', function(event) { + var sender = BrowserWindow.fromWebContents(event.sender); + githubOauthFlow(settings, function(err, user) { + if (err) { + sender.send('github-oauth-flow-error', err); + } else { + sender.send('github-oauth-flow-complete', user); + } + }); + }); + + ipc.on('mark-setup-complete', function() { + setup.markComplete(function() { + debug('setup marked complete'); + }); + }); + + setup(); +}); diff --git a/src/electron/setup.js b/src/electron/setup.js new file mode 100644 index 00000000000..7bc35cc76b0 --- /dev/null +++ b/src/electron/setup.js @@ -0,0 +1,41 @@ +var fs = require('fs'); +var path = require('path'); +var app = require('app'); +var debug = require('debug')('scout:electron:setup'); + +var SETUP_PATH = path.join(app.getPath('userData'), 'setup-completed.json'); +var SETUP_VERSION = '1.0.0'; + +module.exports = function showSetupOrStart() { + fs.exists(SETUP_PATH, function(exists) { + if (!exists) { + debug('no setup-completed.json yet'); + return app.emit('open-setup-dialog'); + } + fs.readFile(SETUP_PATH, function(err, buf) { + if (err) return console.log(err); + + var d; + try { + d = JSON.parse(buf); + } catch (e) { + return console.log(err); + } + debug('user completed setup version %s at %s', d.version, d.completed_at); + if (d.version !== SETUP_VERSION) { + debug('new setup version available so showing setup again.'); + return app.emit('open-setup-dialog'); + } + // @todo (imlucas): Restore windows instead of bringing up connect all the time... + app.emit('open-connect-dialog'); + }); + }); +}; + +module.exports.markCompleted = function(done) { + var data = { + version: SETUP_VERSION, + completed_at: new Date() + }; + fs.writeFile(SETUP_PATH, JSON.stringify(data), done); +}; diff --git a/src/electron/window-manager.js b/src/electron/window-manager.js index 9adc3656466..880a94901f2 100644 --- a/src/electron/window-manager.js +++ b/src/electron/window-manager.js @@ -1,5 +1,4 @@ var _ = require('lodash'); - var BrowserWindow = require('browser-window'); var app = require('app'); var debug = require('debug')('scout-electron:window-manager'); @@ -8,12 +7,12 @@ var path = require('path'); var RESOURCES = path.resolve(__dirname, '../../'); var DEFAULT_URL = 'file://' + path.join(RESOURCES, 'index.html#connect'); +var SETUP_URL = 'file://' + path.join(RESOURCES, 'index.html#setup'); var DEFAULT_WIDTH = 1024; var DEFAULT_HEIGHT = 700; var DEFAULT_HEIGHT_DIALOG; - if (process.platform === 'win32') { DEFAULT_HEIGHT_DIALOG = 460; } else if (process.platform === 'linux') { @@ -21,27 +20,28 @@ if (process.platform === 'win32') { } else { DEFAULT_HEIGHT_DIALOG = 400; } + +var ICON = path.join(__dirname, '..', '..', 'images', 'mongodb-leaf.png'); var DEFAULT_WIDTH_DIALOG = 600; var connectWindow; -var windowsOpenCount = 0; +var setupWindow; module.exports.create = function(opts) { opts = _.defaults(opts || {}, { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, - url: DEFAULT_URL + url: DEFAULT_URL, + icon: ICON }); - debug('creating new window'); - var _window = new BrowserWindow({ - width: opts.width, - height: opts.height, - 'web-preferences': { - 'subpixel-font-scaling': true, - 'direct-write': true - } + opts['web-preferences'] = _.defaults(opts['web-preferences'] || {}, { + 'subpixel-font-scaling': true, + 'direct-write': true }); + + debug('creating new window'); + var _window = new BrowserWindow(opts); attachMenu(_window); _window.loadUrl(opts.url); @@ -60,18 +60,21 @@ module.exports.create = function(opts) { connectWindow = null; }); } - windowsOpenCount++; - _window.on('closed', function() { - windowsOpenCount--; - if (windowsOpenCount === 0) { - debug('all windows closed. quitting.'); - app.quit(); - } - }); + + if (opts.url === SETUP_URL) { + setupWindow = _window; + setupWindow.on('closed', function() { + debug('setup window closed.'); + setupWindow = null; + }); + } + + debug('emitting `window-opened`'); + app.emit('window-opened', _window); return _window; }; -app.on('show connect dialog', function(opts) { +module.exports.openConnectDialog = function(opts) { if (connectWindow) { connectWindow.focus(); return connectWindow; @@ -81,11 +84,25 @@ app.on('show connect dialog', function(opts) { opts = _.extend(opts || {}, { height: DEFAULT_HEIGHT_DIALOG, width: DEFAULT_WIDTH_DIALOG, - url: DEFAULT_URL + centered: true }); - module.exports.create(opts); -}); + return module.exports.create(opts); +}; + +module.exports.openSetupDialog = function(opts) { + if (setupWindow) { + setupWindow.focus(); + return setupWindow; + } -app.on('ready', function() { - app.emit('show connect dialog'); -}); + opts = opts || {}; + opts = _.extend(opts || {}, { + url: SETUP_URL, + height: 550, + width: 600, + centered: true, + 'always-on-top': true, + resizable: false + }); + return module.exports.create(opts); +}; From 0edd4d01034994253f4a6e0222d30c8dc91abecd Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 18:57:36 -0400 Subject: [PATCH 10/30] fixup intercom + new user props github provides --- src/intercom.js | 44 +++++++++++++++++++++++++++++++++++++------- src/models/user.js | 10 +++++++++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/intercom.js b/src/intercom.js index 8be2dc9cd23..745b887e876 100644 --- a/src/intercom.js +++ b/src/intercom.js @@ -1,5 +1,7 @@ var _ = require('lodash'); var pkg = require('../package.json'); +var app = require('ampersand-app'); +var debug = require('debug')('scout:intercom'); var i = function() { i.c(arguments); @@ -24,13 +26,46 @@ module.exports.update = function() { // whatever events it needs to as well. module.exports.track = function(eventName, data) { data = _.extend(data || {}, { - app_version: pkg.version + 'App Version': pkg.version }); window.Intercom('trackEvent', eventName, data); }; +function boot() { + var config = _.extend(app.user.toJSON(), { + app_id: 'p57suhg7' + }); + config.user_id = app.user.id; + debug('Syncing user info w/ intercom', config); + window.Intercom('boot', config); +} + +module.exports.open = function(opts) { + if (opts && opts.message) { + window.Intercom('showNewMessage', opts.message); + } else { + window.Intercom('show'); + } +}; + +module.exports.show = function() { + var el = document.querySelector('#intercom-container .intercom-launcher'); + if (el) { + el.classList.remove('hidden'); + } +}; + +module.exports.hide = function() { + var el = document.querySelector('#intercom-container .intercom-launcher'); + if (!el) { + return setTimeout(module.exports.hide, 100); + } + el.classList.add('hidden'); +}; + /** * Injects the intercom client script. + * @param {models.User} user - The current user. */ module.exports.inject = function(user) { var head = document.getElementsByTagName('head')[0]; @@ -38,10 +73,5 @@ module.exports.inject = function(user) { script.type = 'text/javascript'; script.src = 'https://widget.intercom.io/widget/p57suhg7'; head.appendChild(script); - - var config = _.extend(user.toJSON(), { - app_id: 'p57suhg7' - }); - config.user_id = user.id; - window.Intercom('boot', config); + boot(); }; diff --git a/src/models/user.js b/src/models/user.js index afd54ae74bf..4d966351ded 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -8,7 +8,15 @@ var User = Model.extend({ id: 'string', name: 'string', email: 'string', - created_at: 'date' + created_at: 'date', + avatar_url: 'string', + company_name: 'string', + github_username: 'string', + /** + * `public_repos + public_gists + followers + following` + */ + github_score: 'number', + github_last_activity_at: 'date' }, sync: localforage('User') }); From b5926e1b73579372638a8c7d76f37f61bef7db69 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 19:02:19 -0400 Subject: [PATCH 11/30] deps for github that were stomped --- gulpfile.js | 2 +- package.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 04c3585b335..5165bb80260 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -147,7 +147,7 @@ gulp.task('copy:images', function() { gulp.task('copy:electron', function() { return merge( - gulp.src(['main.js', 'package.json', 'settings.json', 'README.md']) + gulp.src(['main.js', 'package.json', 'node_modules/animate.css/animate.css', 'README.md']) .pipe(gulp.dest('build/')), gulp.src(['src/electron/*']) .pipe(gulp.dest('build/src/electron')) diff --git a/package.json b/package.json index ad50f1f3809..37a486654b1 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,8 @@ "ampersand-sync-localforage": "^0.1.1", "ampersand-view": "^8.0.0", "ampersand-view-switcher": "^2.0.0", + "animate.css": "^3.2.5", + "animationend": "0.0.1", "bootstrap": "https://github.com/twbs/bootstrap/archive/v3.3.5.tar.gz", "bugsnag-js": "^2.4.8", "d3": "^3.5.5", @@ -88,10 +90,12 @@ "newrelic": "^1.21.1", "numeral": "^1.5.3", "octicons": "https://github.com/github/octicons/archive/v2.2.0.tar.gz", + "octonode": "^0.7.1", "phantomjs-polyfill": "0.0.1", "pluralize": "^1.1.2", "qs": "^3.1.0", "raf": "^3.0.0", + "request": "^2.60.0", "scout-brain": "http://bin.mongodb.org/js/scout-brain/v0.0.2/scout-brain-0.0.2.tar.gz", "scout-client": "http://bin.mongodb.org/js/scout-client/v0.0.5/scout-client-0.0.5.tar.gz", "scout-server": "http://bin.mongodb.org/js/scout-server/v0.0.4/scout-server-0.0.4.tar.gz", From e109e50ddc0febe79f9bcf089d98d30edcf35094 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sat, 1 Aug 2015 19:07:39 -0400 Subject: [PATCH 12/30] whip end-to-end setup wizard --- src/index.less | 1 + src/router.js | 17 ++++++++- src/setup/connect-github.jade | 7 ++++ src/setup/connect-github.js | 29 +++++++++++++++ src/setup/connect-mongodb.jade | 11 ++++++ src/setup/connect-mongodb.js | 11 ++++++ src/setup/finished.jade | 2 + src/setup/finished.js | 11 ++++++ src/setup/index.jade | 2 + src/setup/index.js | 68 ++++++++++++++++++++++++++++++++++ src/setup/index.less | 7 ++++ src/setup/user-info.jade | 27 ++++++++++++++ src/setup/user-info.js | 28 ++++++++++++++ src/setup/welcome.jade | 8 ++++ src/setup/welcome.js | 17 +++++++++ 15 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/setup/connect-github.jade create mode 100644 src/setup/connect-github.js create mode 100644 src/setup/connect-mongodb.jade create mode 100644 src/setup/connect-mongodb.js create mode 100644 src/setup/finished.jade create mode 100644 src/setup/finished.js create mode 100644 src/setup/index.jade create mode 100644 src/setup/index.js create mode 100644 src/setup/index.less create mode 100644 src/setup/user-info.jade create mode 100644 src/setup/user-info.js create mode 100644 src/setup/welcome.jade create mode 100644 src/setup/welcome.js diff --git a/src/index.less b/src/index.less index d8580352043..a94d4436a1b 100644 --- a/src/index.less +++ b/src/index.less @@ -9,4 +9,5 @@ @import "./src/minicharts/index.less"; @import "./src/refine-view/index.less"; @import "./src/sampling-message/index.less"; +@import "./src/setup/index.less"; @import "./src/statusbar/index.less"; diff --git a/src/router.js b/src/router.js index 396c8571bbf..ea125845567 100644 --- a/src/router.js +++ b/src/router.js @@ -2,20 +2,25 @@ var AmpersandRouter = require('ampersand-router'); var intercom = require('./intercom'); var HomePage = require('./home'); var Connect = require('./connect'); +var Setup = require('./setup'); +var app = require('ampersand-app'); module.exports = AmpersandRouter.extend({ routes: { '': 'index', schema: 'index', connect: 'connect', + 'setup': 'setup', + 'setup/:step': 'setup', 'schema/:ns': 'schema', - '(*path)': 'catchAll' + '(*path)': 'catchAll', }, index: function() { intercom.track('Connected to MongoDB'); this.trigger('page', new HomePage({})); }, schema: function(ns) { + app.intercom.show(); this.trigger('page', new HomePage({ ns: ns })); @@ -24,7 +29,15 @@ module.exports = AmpersandRouter.extend({ this.redirectTo(''); }, connect: function() { - intercom.track('App Launched'); + app.intercom.hide(); + intercom.track('Open Connect Dialog'); this.trigger('page', new Connect({})); + }, + setup: function(step) { + app.intercom.hide(); + intercom.track('Open Setup'); + this.trigger('page', new Setup({ + step: parseInt(step || 1, 10) + })); } }); diff --git a/src/setup/connect-github.jade b/src/setup/connect-github.jade new file mode 100644 index 00000000000..224c960b107 --- /dev/null +++ b/src/setup/connect-github.jade @@ -0,0 +1,7 @@ +div.animated + .row + .col-xs-12 + h3.h4 Do you have a GitHub account? + a.btn.btn-default + i.fa.fa-fw.fa-github + | Sign In To GitHub diff --git a/src/setup/connect-github.js b/src/setup/connect-github.js new file mode 100644 index 00000000000..82dfd7136d0 --- /dev/null +++ b/src/setup/connect-github.js @@ -0,0 +1,29 @@ +var View = require('ampersand-view'); +var app = require('ampersand-app'); + +module.exports = View.extend({ + events: { + 'click [data-hook=connect-github]': 'onGithubClicked', + 'click [data-hook=skip]': 'onSkipClicked' + }, + template: require('./connect-github.jade'), + onGithubClicked: function(evt) { + evt.preventDefault(); + app.ipc.send('open-github-oauth-flow'); + app.ipc.once('github-oauth-flow-error', function(err) { + console.error(err); + this.parent.step++; + }.bind(this)); + app.ipc.once('github-oauth-flow-complete', function(data) { + app.user.set(data); + app.user.save(); + this.parent.name = data.name; + this.parent.email = data.email; + this.parent.step++; + }.bind(this)); + }, + onSkipClicked: function(evt) { + evt.preventDefault(); + this.parent.step++; + } +}); diff --git a/src/setup/connect-mongodb.jade b/src/setup/connect-mongodb.jade new file mode 100644 index 00000000000..5f1fd310381 --- /dev/null +++ b/src/setup/connect-mongodb.jade @@ -0,0 +1,11 @@ +div.animated + h1 Connect to MongoDB + form.form-horizontal.col-sm-12 + .form-group + input.form-control.input-lg(type='text', name='hostname', placeholder='localhost') + span.form-control-feedback + fa.fa-fw.fa-check + .form-group + input.form-control.input-lg(type='number', name='port', placeholder='27017') + .form-group + button.btn.btn-primary.btn-lg(data-hook='to-step-4') Continue diff --git a/src/setup/connect-mongodb.js b/src/setup/connect-mongodb.js new file mode 100644 index 00000000000..532f4decf28 --- /dev/null +++ b/src/setup/connect-mongodb.js @@ -0,0 +1,11 @@ +var View = require('ampersand-view'); +module.exports = View.extend({ + events: { + 'click [data-hook=continue]': 'onContinueClicked' + }, + template: require('./connect-mongodb.jade'), + onContinueClicked: function(evt) { + evt.preventDefault(); + this.parent.step++; + } +}); diff --git a/src/setup/finished.jade b/src/setup/finished.jade new file mode 100644 index 00000000000..7c62acc4e27 --- /dev/null +++ b/src/setup/finished.jade @@ -0,0 +1,2 @@ +.animated + h1 finished! diff --git a/src/setup/finished.js b/src/setup/finished.js new file mode 100644 index 00000000000..13afa5643d2 --- /dev/null +++ b/src/setup/finished.js @@ -0,0 +1,11 @@ +var View = require('ampersand-view'); +module.exports = View.extend({ + events: { + 'click [data-hook=continue]': 'onContinueClicked' + }, + template: require('./finished.jade'), + onContinueClicked: function(evt) { + evt.preventDefault(); + this.parent.complete(); + } +}); diff --git a/src/setup/index.jade b/src/setup/index.jade new file mode 100644 index 00000000000..3dca1b2b505 --- /dev/null +++ b/src/setup/index.jade @@ -0,0 +1,2 @@ +.page: .content.container-fluid: .row: .col-sm-8.col-sm-push-2 + div(data-hook='step-container') diff --git a/src/setup/index.js b/src/setup/index.js new file mode 100644 index 00000000000..c9ba44ba7dd --- /dev/null +++ b/src/setup/index.js @@ -0,0 +1,68 @@ +var View = require('ampersand-view'); +var ViewSwitcher = require('ampersand-view-switcher'); +var onAnimationEnd = require('animationend'); +var app = require('ampersand-app'); + +var stepKlasses = [ + require('./welcome'), + // require('./connect-github'), + require('./user-info'), + require('./connect-mongodb'), + require('./finished') +]; + +var FirstRunView = View.extend({ + props: { + step: { + type: 'number', + default: 1 + }, + name: { + type: 'string' + }, + email: { + type: 'string' + } + }, + goToStep: function(n) { + this.switcher.set(this.steps[n - 1]); + app.navigate('first-run/' + n, { + silent: true + }); + }, + initialize: function() { + this.listenTo(this, 'change:step', function(view, newVal) { + this.goToStep(newVal); + }); + + this.steps = stepKlasses.map(function(Klass) { + return new Klass({ + parent: this + }); + }.bind(this)); + }, + template: require('./index.jade'), + render: function() { + this.renderWithTemplate(); + this.stepContainer = this.queryByHook('step-container'); + this.switcher = new ViewSwitcher(this.stepContainer, { + waitForRemove: true, + hide: function(oldView, cb) { + onAnimationEnd(oldView.el, function() { + setTimeout(cb, 200); + }); + oldView.el.classList.add('bounceOutLeft'); + }, + show: function(newView) { + newView.el.classList.add('bounceInRight'); + } + }); + this.goToStep(this.step); + document.title = 'Welcome to MongoDB Scout'; + }, + complete: function() { + app.ipc.send('mark-setup-complete'); + // @todo: ipc send mongodb:// to open schema in new window? + } +}); +module.exports = FirstRunView; diff --git a/src/setup/index.less b/src/setup/index.less new file mode 100644 index 00000000000..23f1f09ee5a --- /dev/null +++ b/src/setup/index.less @@ -0,0 +1,7 @@ +@import "animate.css"; +input:invalid { + border: 1px solid red; +} +input:valid { + border: 1px solid green; +} diff --git a/src/setup/user-info.jade b/src/setup/user-info.jade new file mode 100644 index 00000000000..caf3a8e2f31 --- /dev/null +++ b/src/setup/user-info.jade @@ -0,0 +1,27 @@ +div.animated + h1(style='margin-bottom: 20px;') Tell us about yourself + form.form-horizontal.col-sm-12 + .form-group.has-feedback: input.form-control.input-lg( + type='text', + required='required', + autocomplete='billing name', + name='name', + minlength=2, + maxlength=128, + title='Let\'s not be strangers...', + placeholder='What is your name?' + ) + .form-group: input.form-control.input-lg( + type='email', + required='required', + autocomplete='email' + name='email', + minlength=2, + maxlength=128, + title='How will we contact you if we have questions?...', + placeholder='And your email address?' + ) + .form-group: button.btn.btn-primary.btn-lg( + type='submit', + data-hook='continue' + ) Continue diff --git a/src/setup/user-info.js b/src/setup/user-info.js new file mode 100644 index 00000000000..2369c71aec5 --- /dev/null +++ b/src/setup/user-info.js @@ -0,0 +1,28 @@ +var View = require('ampersand-view'); +var debug = require('debug')('scout:first-run:user-info'); + +module.exports = View.extend({ + events: { + 'click [data-hook=continue]': 'onContinueClicked' + }, + template: require('./user-info.jade'), + onContinueClicked: function(evt) { + evt.preventDefault(); + debug('validitity?', this.form.checkValidity()); + debugger; + }, + render: function() { + this.renderWithTemplate(); + this.form = this.query('form'); + this.emailInput = this.query('input[name=email]'); + this.nameInput = this.query('input[name=name]'); + + setTimeout(function() { + this.nameInput.focus(); + }.bind(this), 400); + this.form.addEventListener('submit', function(event) { + debug('validitity?', this.checkValidity()); + event.preventDefault(); + }, false); + } +}); diff --git a/src/setup/welcome.jade b/src/setup/welcome.jade new file mode 100644 index 00000000000..40f074d168d --- /dev/null +++ b/src/setup/welcome.jade @@ -0,0 +1,8 @@ +div.animated + div(style='text-align: center; margin-top: 50px;margin-bottom: 50px;') + img.animated(data-hook='leaf', src='images/mongodb-leaf_256x256.png') + + h1(style='text-align: center;') Thanks for trying MongoDB Scout! + .small Let's take a minute to setup your computer. + .row(style=';text-align: center;') + button.btn.btn-primary.btn-lg(data-hook='continue') Continue diff --git a/src/setup/welcome.js b/src/setup/welcome.js new file mode 100644 index 00000000000..964589e3f79 --- /dev/null +++ b/src/setup/welcome.js @@ -0,0 +1,17 @@ +var View = require('ampersand-view'); +module.exports = View.extend({ + events: { + 'click [data-hook=continue]': 'onContinueClicked' + }, + template: require('./welcome.jade'), + onContinueClicked: function(evt) { + evt.preventDefault(); + this.parent.step++; + }, + render: function() { + this.renderWithTemplate(); + setTimeout(function() { + this.queryByHook('leaf').classList.add('rubberBand'); + }.bind(this), 500); + } +}); From a3c0ad14b61ea28602624c95638185ab67cb0064 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 2 Aug 2015 12:48:15 -0400 Subject: [PATCH 13/30] remove CSP until we fully understand it --- src/index.jade | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.jade b/src/index.jade index c722848ddb8..426a2731aae 100644 --- a/src/index.jade +++ b/src/index.jade @@ -2,8 +2,6 @@ doctype html html(lang='en') head title MongoDB - meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'self' http://localhost:35729 https://widget.intercom.io https://js.intercomcdn.com/; style-src 'self' 'unsafe-inline';") - meta(name='viewport', content='initial-scale=1') link(rel='stylesheet', href='index.css', charset='UTF-8') body From dc6b9a099659679b075a8d0f712820259f7e30d6 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 2 Aug 2015 12:48:53 -0400 Subject: [PATCH 14/30] simplify and remove dupe code --- src/electron/index.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/electron/index.js b/src/electron/index.js index 989e3ef7dcc..ceceec4eb27 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -25,17 +25,8 @@ app.on('window-all-closed', function() { app.quit(); }); -app.on('window-all-closed', function() { - app.quit(); -}); - -app.on('open-setup-dialog', function(opts) { - windows.openSetupDialog(opts); -}); - -app.on('open-connect-dialog', function(opts) { - windows.openConnectDialog(opts); -}); +app.on('open-setup-dialog', windows.openSetupDialog); +app.on('open-connect-dialog', windows.openConnectDialog); app.on('ready', function() { ipc.on('open-setup-dialog', app.emit.bind(app, 'open-setup-dialog')); From 8e3f4f5cd77e79a020a80feb9edb032fea048230 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 2 Aug 2015 12:49:50 -0400 Subject: [PATCH 15/30] missed a url replace on first-run -> setup rename --- src/setup/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup/index.js b/src/setup/index.js index c9ba44ba7dd..190f3bebe0b 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -26,7 +26,7 @@ var FirstRunView = View.extend({ }, goToStep: function(n) { this.switcher.set(this.steps[n - 1]); - app.navigate('first-run/' + n, { + app.navigate('setup/' + n, { silent: true }); }, From 3348844c6d6cda8d0c21ad5785a495abac3c059e Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 2 Aug 2015 14:39:02 -0400 Subject: [PATCH 16/30] "Right-click -> inspect element" --- src/app.js | 2 ++ src/context-menu-manager.js | 50 +++++++++++++++++++++++++++++ src/electron/index.js | 29 +++++++++++++++++ src/electron/inject-app-bindings.js | 9 ++++++ src/electron/livereload.js | 7 +++- src/index.jade | 10 ++---- src/setup/index.js | 2 ++ 7 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 src/context-menu-manager.js create mode 100644 src/electron/inject-app-bindings.js diff --git a/src/app.js b/src/app.js index 849cd49e50c..5892669a3ee 100644 --- a/src/app.js +++ b/src/app.js @@ -155,6 +155,8 @@ var User = require('./models/user'); var Router = require('./router'); var Statusbar = require('./statusbar'); +require('./context-menu-manager'); + app.extend({ client: null, meta: { diff --git a/src/context-menu-manager.js b/src/context-menu-manager.js new file mode 100644 index 00000000000..5dfe0c3c150 --- /dev/null +++ b/src/context-menu-manager.js @@ -0,0 +1,50 @@ +/** + * Making context menus work (aka right-click menu) involes 2 exchanges + * between the web-page and the main processes: + * + * 1. `web-page` adds a listener for a document `contextmenu` event and sends + * a `show-context-menu` message with `template`. + * 2. `main` catches `show-context-menu`, adds proper click handlers for `template` + * and calls `electron#Menu.buildFromTemplate(template).popup()` to make the + * context menu actually appear. + * 3. When a menu item is actually clicked, `main` sends a `run-command` message + * to the owning `web-page`. + * 4. `web-page` calls `electron#ipc.send(item.command, item.opts)` + * 5. If `main` is listening for `item.command`, it will be executed. + * + * @see https://github.com/atom/atom/blob/master/src/context-menu-manager.coffee + */ +var app = require('ampersand-app'); +var $ = require('jquery'); +var debug = require('debug')('scout:context-menu-manager'); + +function ContextMenuManager() { + $(document).on('contextmenu', this.showForEvent.bind(this)); +} + +ContextMenuManager.prototype.showForEvent = function(event) { + debug('show for event'); + event.preventDefault(); + var menuTemplate = this.getTemplateForEvent(event); + if (menuTemplate.length > 0) { + debug('sending show-context-menu for template', menuTemplate); + app.ipc.send('show-context-menu', { + template: menuTemplate + }); + } +}; + +ContextMenuManager.prototype.getTemplateForEvent = function(event) { + var template = []; + template.push({ + label: 'Inspect Element', + command: 'devtools-inspect-element', + opts: { + x: event.pageX, + y: event.pageY + } + }); + return template; +}; + +module.exports = new ContextMenuManager(); diff --git a/src/electron/index.js b/src/electron/index.js index ceceec4eb27..43090154f04 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -6,6 +6,7 @@ if (process.env.NODE_ENV === 'development') { var app = require('app'); var BrowserWindow = require('browser-window'); +var Menu = require('menu'); var ipc = require('ipc'); var windows = require('./window-manager'); var githubOauthFlow = require('./github-oauth-flow'); @@ -29,6 +30,34 @@ app.on('open-setup-dialog', windows.openSetupDialog); app.on('open-connect-dialog', windows.openConnectDialog); app.on('ready', function() { + ipc.on('devtools-inspect-element', function(event, opts) { + debug('devtools-inspect-element', opts); + var sender = BrowserWindow.fromWebContents(event.sender); + if (!sender.isDevToolsOpened()) { + debug('opening devtools'); + sender.openDevTools(); + } + sender.inspectElement(opts.x, opts.y); + }); + + ipc.on('show-context-menu', function(event, opts) { + debug('show-context-menu', opts); + var sender = BrowserWindow.fromWebContents(event.sender); + var template = opts.template.map(function(item) { + debug('creating click handler for ipc', item.command, item.opts); + item.click = function() { + var msg = { + command: item.command, + opts: item.opts + }; + debug('telling browser window to run command', msg); + sender.send('run-command', msg); + }; + return item; + }); + debug('building and popping up', template); + Menu.buildFromTemplate(template).popup(sender); + }); ipc.on('open-setup-dialog', app.emit.bind(app, 'open-setup-dialog')); ipc.on('open-connect-dialog', app.emit.bind(app, 'open-connect-dialog')); ipc.on('open-github-oauth-flow', function(event) { diff --git a/src/electron/inject-app-bindings.js b/src/electron/inject-app-bindings.js new file mode 100644 index 00000000000..d905d33350b --- /dev/null +++ b/src/electron/inject-app-bindings.js @@ -0,0 +1,9 @@ +// Make electron ipc available via `app`. +window.app.use(function(app) { + app.ipc = require('ipc'); + app.trigger('change:ipc'); + + app.ipc.on('run-command', function(data) { + app.ipc.send(data.command, data.opts); + }); +}); diff --git a/src/electron/livereload.js b/src/electron/livereload.js index f4f2c6bb39e..835d41e234c 100644 --- a/src/electron/livereload.js +++ b/src/electron/livereload.js @@ -2,6 +2,7 @@ var path = require('path'); var watch = require('watch'); var tinyLR = require('tiny-lr'); var debounce = require('lodash').debounce; +var _ = require('lodash'); var debug = require('debug')('scout:electron:livereload'); var NODE_MODULES_REGEX = /node_modules/; @@ -21,7 +22,11 @@ livereload.listen(opts.port, opts.host); debug('livereload server started on %s:%d', opts.host, opts.port); var onFileschanged = debounce(function(files) { - debug('File change detected! Sending reload message', Object.keys(files)); + // On startup, `files` is an `fs.Stat` of the directory + // being watched and should not send a reload message. + if (_.isPlainObject(files)) return; + + debug('File change detected! Sending reload message', files); livereload.changed({ body: { files: files diff --git a/src/index.jade b/src/index.jade index 426a2731aae..3b1403388f4 100644 --- a/src/index.jade +++ b/src/index.jade @@ -2,6 +2,8 @@ doctype html html(lang='en') head title MongoDB + meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'self' http://localhost:35729 https://widget.intercom.io https://js.intercomcdn.com/; style-src 'self' 'unsafe-inline';") + meta(name='viewport', content='initial-scale=1') link(rel='stylesheet', href='index.css', charset='UTF-8') body @@ -12,13 +14,7 @@ html(lang='en') if NODE_ENV === 'development' //- Include the livereload client so pages reload automatically when changed. script(src='http://localhost:35729/livereload.js', charset='UTF-8') - script(src='src/electron/development-debug-header.js', charset='UTF-8') script(src='index.js', charset='UTF-8') - script(type='text/javascript'). - //- Make electron ipc available via `app`. - window.app.use(function(app){ - app.ipc = require('ipc'); - app.trigger('change:ipc'); - }); + script(src='src/electron/inject-app-bindings.js', charset='UTF-8') diff --git a/src/setup/index.js b/src/setup/index.js index 190f3bebe0b..7ed067a9833 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -2,6 +2,7 @@ var View = require('ampersand-view'); var ViewSwitcher = require('ampersand-view-switcher'); var onAnimationEnd = require('animationend'); var app = require('ampersand-app'); +var debug = require('debug')('scout:setup'); var stepKlasses = [ require('./welcome'), @@ -25,6 +26,7 @@ var FirstRunView = View.extend({ } }, goToStep: function(n) { + debug('going to step %d', n); this.switcher.set(this.steps[n - 1]); app.navigate('setup/' + n, { silent: true From 25785993694f186c18f8d503c5dc58eee2049733 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Tue, 4 Aug 2015 16:39:06 -0400 Subject: [PATCH 17/30] more progress on sign up form validation --- src/electron/window-manager.js | 14 +++++++------- src/setup/index.less | 12 +++++++----- src/setup/user-info.js | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/electron/window-manager.js b/src/electron/window-manager.js index 880a94901f2..8fa8d71988e 100644 --- a/src/electron/window-manager.js +++ b/src/electron/window-manager.js @@ -6,7 +6,7 @@ var attachMenu = require('./menu'); var path = require('path'); var RESOURCES = path.resolve(__dirname, '../../'); -var DEFAULT_URL = 'file://' + path.join(RESOURCES, 'index.html#connect'); +var CONNECT_URL = 'file://' + path.join(RESOURCES, 'index.html#connect'); var SETUP_URL = 'file://' + path.join(RESOURCES, 'index.html#setup'); var DEFAULT_WIDTH = 1024; @@ -31,8 +31,9 @@ module.exports.create = function(opts) { opts = _.defaults(opts || {}, { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, - url: DEFAULT_URL, - icon: ICON + url: CONNECT_URL, + icon: ICON, + centered: true }); opts['web-preferences'] = _.defaults(opts['web-preferences'] || {}, { @@ -53,7 +54,7 @@ module.exports.create = function(opts) { }); }); - if (opts.url === DEFAULT_URL) { + if (opts.url === CONNECT_URL) { connectWindow = _window; connectWindow.on('closed', function() { debug('connect window closed.'); @@ -82,9 +83,10 @@ module.exports.openConnectDialog = function(opts) { opts = opts || {}; opts = _.extend(opts || {}, { + url: CONNECT_URL, height: DEFAULT_HEIGHT_DIALOG, width: DEFAULT_WIDTH_DIALOG, - centered: true + resizable: false }); return module.exports.create(opts); }; @@ -100,8 +102,6 @@ module.exports.openSetupDialog = function(opts) { url: SETUP_URL, height: 550, width: 600, - centered: true, - 'always-on-top': true, resizable: false }); return module.exports.create(opts); diff --git a/src/setup/index.less b/src/setup/index.less index 23f1f09ee5a..80a0d82759f 100644 --- a/src/setup/index.less +++ b/src/setup/index.less @@ -1,7 +1,9 @@ @import "animate.css"; -input:invalid { - border: 1px solid red; -} -input:valid { - border: 1px solid green; +form.validated { + input:invalid { + border: 1px solid red; + } + input:valid { + border: 1px solid green; + } } diff --git a/src/setup/user-info.js b/src/setup/user-info.js index 2369c71aec5..1e60de88ea2 100644 --- a/src/setup/user-info.js +++ b/src/setup/user-info.js @@ -2,14 +2,28 @@ var View = require('ampersand-view'); var debug = require('debug')('scout:first-run:user-info'); module.exports = View.extend({ + props: { + validated: { + type: 'boolean', + default: false + } + }, events: { 'click [data-hook=continue]': 'onContinueClicked' }, template: require('./user-info.jade'), onContinueClicked: function(evt) { evt.preventDefault(); - debug('validitity?', this.form.checkValidity()); - debugger; + this.validate(); + if (this.is_valid) { + + } else { + + } + }, + validate: function() { + this.validated = true; + this.is_valid = this.form.checkValidity(); }, render: function() { this.renderWithTemplate(); From 3b66c132a25407b71bde84b8bca0dad437589105 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 5 Aug 2015 11:45:24 -0400 Subject: [PATCH 18/30] more form validation --- src/setup/user-info.js | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/setup/user-info.js b/src/setup/user-info.js index 1e60de88ea2..630bdd146ca 100644 --- a/src/setup/user-info.js +++ b/src/setup/user-info.js @@ -8,22 +8,48 @@ module.exports = View.extend({ default: false } }, + binding: { + validated: { + type: 'booleanClass', + yes: 'validated' + } + }, events: { - 'click [data-hook=continue]': 'onContinueClicked' + 'click [data-hook=continue]': 'onSubmit' }, template: require('./user-info.jade'), - onContinueClicked: function(evt) { + onSubmit: function(evt) { + debug('submitted'); evt.preventDefault(); - this.validate(); - if (this.is_valid) { + this.validated = true; + this.is_valid = this.form.checkValidity(); - } else { + var emailValid = this.validateInput(this.emailInput); + debug('email valid?', emailValid); + + var nameValid = this.validateInput(this.nameInput); + debug('name valid?', nameValid); + + debugger; + if (this.is_valid) { } }, - validate: function() { - this.validated = true; - this.is_valid = this.form.checkValidity(); + validateInput: function(input) { + var isValid = input.validity.valid; + var toAdd; + var toRemove; + + if (isValid) { + toAdd = 'has-error'; + toRemove = 'has-success'; + } else { + toAdd = 'has-success'; + toRemove = 'has-error'; + } + input.parentNode.classList.add(toAdd); + input.parentNode.classList.remove(toRemove); + return isValid; }, render: function() { this.renderWithTemplate(); From 78499b1a83647118eaa8b501dcc195b3cee5c28d Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 5 Aug 2015 13:03:44 -0400 Subject: [PATCH 19/30] Finish user-form and validation. --- src/setup/index.js | 5 +++++ src/setup/index.less | 42 +++++++++++++++++++++++++++++++++++----- src/setup/user-info.jade | 36 ++++++++++++++++++++++------------ src/setup/user-info.js | 38 ++++++++++++++++++++++-------------- 4 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/setup/index.js b/src/setup/index.js index 7ed067a9833..d84743f8c1d 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -63,6 +63,11 @@ var FirstRunView = View.extend({ document.title = 'Welcome to MongoDB Scout'; }, complete: function() { + app.user.set({ + name: this.name, + email: this.email + }); + app.user.save(); app.ipc.send('mark-setup-complete'); // @todo: ipc send mongodb:// to open schema in new window? } diff --git a/src/setup/index.less b/src/setup/index.less index 80a0d82759f..8690a85478d 100644 --- a/src/setup/index.less +++ b/src/setup/index.less @@ -1,9 +1,41 @@ @import "animate.css"; -form.validated { - input:invalid { - border: 1px solid red; + +.setup-user-info { + h1 { + margin-bottom: 20px; } - input:valid { - border: 1px solid green; + form { + .form-group { + .help-block { + .lead; + padding-top: 10px; + padding-left: 16px; + display: block; + } + .on-error, .on-success { + display: none; + } + + .on-start { + display: block; + } + + &.has-error { + .on-error { + display: block; + } + .on-start, .on-success { + display: none; + } + } + &.has-success { + .on-success { + display: block; + } + .on-start, .on-error { + display: none; + } + } + } } } diff --git a/src/setup/user-info.jade b/src/setup/user-info.jade index caf3a8e2f31..383d9acd407 100644 --- a/src/setup/user-info.jade +++ b/src/setup/user-info.jade @@ -1,26 +1,38 @@ -div.animated - h1(style='margin-bottom: 20px;') Tell us about yourself +.setup-user-info.animated + h1 Tell us about yourself form.form-horizontal.col-sm-12 - .form-group.has-feedback: input.form-control.input-lg( + .form-group.has-feedback + input.form-control.input-lg( type='text', required='required', - autocomplete='billing name', name='name', minlength=2, maxlength=128, - title='Let\'s not be strangers...', - placeholder='What is your name?' - ) - .form-group: input.form-control.input-lg( + placeholder='What is your name?') + span.form-control-feedback.on-error: .fa.fa-fw.fa-times + p.help-block.on-error Let's not be strangers… + + span.form-control-feedback.on-success: .fa.fa-fw.fa-check + p.help-block.on-success Nice to meet you! + + p.help-block.on-start + + .form-group.has-feedback + input.form-control.input-lg( type='email', required='required', - autocomplete='email' name='email', minlength=2, maxlength=128, - title='How will we contact you if we have questions?...', - placeholder='And your email address?' - ) + placeholder='And your email address?') + span.form-control-feedback.on-error: .fa.fa-fw.fa-times + p.help-block.on-error How will we contact you if we have questions? + + span.form-control-feedback.on-success: .fa.fa-fw.fa-check + p.help-block.on-success Thanks! + + p.help-block.on-start + .form-group: button.btn.btn-primary.btn-lg( type='submit', data-hook='continue' diff --git a/src/setup/user-info.js b/src/setup/user-info.js index 630bdd146ca..cd9ce5a8ee3 100644 --- a/src/setup/user-info.js +++ b/src/setup/user-info.js @@ -3,26 +3,31 @@ var debug = require('debug')('scout:first-run:user-info'); module.exports = View.extend({ props: { - validated: { + active_validation: { type: 'boolean', default: false + }, + is_valid: { + type: 'boolean', + default: true } }, - binding: { - validated: { + bindings: { + is_valid: { + hook: 'continue', type: 'booleanClass', - yes: 'validated' + no: 'disabled' } }, events: { - 'click [data-hook=continue]': 'onSubmit' + 'click [data-hook=continue]': 'onSubmit', + 'change input': 'onInputChanged' }, template: require('./user-info.jade'), onSubmit: function(evt) { debug('submitted'); evt.preventDefault(); - this.validated = true; - this.is_valid = this.form.checkValidity(); + this.active_validation = true; var emailValid = this.validateInput(this.emailInput); debug('email valid?', emailValid); @@ -30,17 +35,24 @@ module.exports = View.extend({ var nameValid = this.validateInput(this.nameInput); debug('name valid?', nameValid); - debugger; if (this.is_valid) { - + this.parent.set({ + name: this.nameInput.value, + email: this.emailInput.value + }); + this.parent.step++; } }, + onInputChanged: function(evt) { + if (!this.active_validation) return; + this.validateInput(evt.delegateTarget); + }, validateInput: function(input) { var isValid = input.validity.valid; var toAdd; var toRemove; - if (isValid) { + if (!isValid) { toAdd = 'has-error'; toRemove = 'has-success'; } else { @@ -49,6 +61,8 @@ module.exports = View.extend({ } input.parentNode.classList.add(toAdd); input.parentNode.classList.remove(toRemove); + this.is_valid = this.form.checkValidity(); + return isValid; }, render: function() { @@ -60,9 +74,5 @@ module.exports = View.extend({ setTimeout(function() { this.nameInput.focus(); }.bind(this), 400); - this.form.addEventListener('submit', function(event) { - debug('validitity?', this.checkValidity()); - event.preventDefault(); - }, false); } }); From 9693f9bf93495ecd767abd5e4ea86cc047319051 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 5 Aug 2015 13:53:23 -0400 Subject: [PATCH 20/30] Fix from bad merge --- src/app.js | 8 +++++++- src/electron/setup.js | 2 +- src/intercom.js | 2 +- src/setup/connect-mongodb.jade | 3 ++- src/setup/connect-mongodb.js | 1 + src/setup/finished.jade | 3 ++- src/setup/index.js | 18 +++++++++++------- src/setup/user-info.js | 19 +++++++++++++++++-- 8 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/app.js b/src/app.js index 5892669a3ee..d0fdeb167e9 100644 --- a/src/app.js +++ b/src/app.js @@ -162,6 +162,12 @@ app.extend({ meta: { 'App Version': pkg.version }, + openSetupDialog: function() { + app.ipc.send('open-setup-dialog'); + }, + openConnectDialog: function() { + app.ipc.send('open-connect-dialog'); + }, init: function() { state.statusbar = new Statusbar(); this.connection = new Connection(); @@ -173,7 +179,7 @@ app.extend({ this.features = { querybuilder: true }; - + state.user = new User(); state.router = new Router(); this.on('change:ipc', function() { diff --git a/src/electron/setup.js b/src/electron/setup.js index 7bc35cc76b0..24306f6c10f 100644 --- a/src/electron/setup.js +++ b/src/electron/setup.js @@ -32,7 +32,7 @@ module.exports = function showSetupOrStart() { }); }; -module.exports.markCompleted = function(done) { +module.exports.markComplete = function(done) { var data = { version: SETUP_VERSION, completed_at: new Date() diff --git a/src/intercom.js b/src/intercom.js index 745b887e876..d355a06d968 100644 --- a/src/intercom.js +++ b/src/intercom.js @@ -73,5 +73,5 @@ module.exports.inject = function(user) { script.type = 'text/javascript'; script.src = 'https://widget.intercom.io/widget/p57suhg7'; head.appendChild(script); - boot(); + user.on('sync', boot); }; diff --git a/src/setup/connect-mongodb.jade b/src/setup/connect-mongodb.jade index 5f1fd310381..38788e70838 100644 --- a/src/setup/connect-mongodb.jade +++ b/src/setup/connect-mongodb.jade @@ -8,4 +8,5 @@ div.animated .form-group input.form-control.input-lg(type='number', name='port', placeholder='27017') .form-group - button.btn.btn-primary.btn-lg(data-hook='to-step-4') Continue + .btn-group + button.btn.btn-primary.btn-lg(data-hook='continue') Continue diff --git a/src/setup/connect-mongodb.js b/src/setup/connect-mongodb.js index 532f4decf28..8502dd62abd 100644 --- a/src/setup/connect-mongodb.js +++ b/src/setup/connect-mongodb.js @@ -5,6 +5,7 @@ module.exports = View.extend({ }, template: require('./connect-mongodb.jade'), onContinueClicked: function(evt) { + debugger; evt.preventDefault(); this.parent.step++; } diff --git a/src/setup/finished.jade b/src/setup/finished.jade index 7c62acc4e27..59f68a7c44a 100644 --- a/src/setup/finished.jade +++ b/src/setup/finished.jade @@ -1,2 +1,3 @@ .animated - h1 finished! + h1 Finished! + button.btn.btn-primary.btn-lg(data-hook='continue') Connect to MongoDB diff --git a/src/setup/index.js b/src/setup/index.js index d84743f8c1d..f0713e1fe37 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -8,7 +8,7 @@ var stepKlasses = [ require('./welcome'), // require('./connect-github'), require('./user-info'), - require('./connect-mongodb'), + // require('./connect-mongodb'), require('./finished') ]; @@ -23,6 +23,14 @@ var FirstRunView = View.extend({ }, email: { type: 'string' + }, + hostname: { + type: 'string', + default: 'localhost' + }, + port: { + type: 'number', + default: 27017 } }, goToStep: function(n) { @@ -63,13 +71,9 @@ var FirstRunView = View.extend({ document.title = 'Welcome to MongoDB Scout'; }, complete: function() { - app.user.set({ - name: this.name, - email: this.email - }); - app.user.save(); app.ipc.send('mark-setup-complete'); - // @todo: ipc send mongodb:// to open schema in new window? + app.ipc.send('open-connect-dialog'); + setTimeout(window.close, 500); } }); module.exports = FirstRunView; diff --git a/src/setup/user-info.js b/src/setup/user-info.js index cd9ce5a8ee3..ff40f7c4950 100644 --- a/src/setup/user-info.js +++ b/src/setup/user-info.js @@ -1,4 +1,5 @@ var View = require('ampersand-view'); +var app = require('ampersand-app'); var debug = require('debug')('scout:first-run:user-info'); module.exports = View.extend({ @@ -21,7 +22,8 @@ module.exports = View.extend({ }, events: { 'click [data-hook=continue]': 'onSubmit', - 'change input': 'onInputChanged' + 'change input': 'onInputChanged', + 'blur input': 'onInputBlur' }, template: require('./user-info.jade'), onSubmit: function(evt) { @@ -36,18 +38,22 @@ module.exports = View.extend({ debug('name valid?', nameValid); if (this.is_valid) { - this.parent.set({ + app.user.save({ name: this.nameInput.value, email: this.emailInput.value }); this.parent.step++; } }, + onInputBlur: function(evt) { + this.validateInput(evt.delegateTarget); + }, onInputChanged: function(evt) { if (!this.active_validation) return; this.validateInput(evt.delegateTarget); }, validateInput: function(input) { + debug('validating %s', input.name); var isValid = input.validity.valid; var toAdd; var toRemove; @@ -66,12 +72,21 @@ module.exports = View.extend({ return isValid; }, render: function() { + debug('rendering'); this.renderWithTemplate(); this.form = this.query('form'); this.emailInput = this.query('input[name=email]'); this.nameInput = this.query('input[name=name]'); + this.listenTo(app.user, 'change:name', function() { + this.nameInput.value = app.user.name; + }.bind(this)); + + this.listenTo(app.user, 'change:email', function() { + this.emailInput.value = app.user.email; + }.bind(this)); setTimeout(function() { + debug('Focusing on name input'); this.nameInput.focus(); }.bind(this), 400); } From 47d8634ea5c7d5162cadf5b87d1f2e99eedf5aca Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 5 Aug 2015 16:11:08 -0400 Subject: [PATCH 21/30] enable github oauth on setup --- package.json | 1 + src/bugsnag.js | 5 ++++- src/electron/config.js | 31 ++++++++++++++++++++++------ src/electron/github-oauth-flow.js | 34 ++++++++++++++++++++----------- src/electron/index.js | 11 ++-------- src/intercom.js | 4 ---- src/metrics.js | 18 ++++++++++++++++ src/setup/connect-github.jade | 14 ++++++++----- src/setup/connect-github.js | 24 +++++++++++++++++++--- src/setup/index.js | 2 +- src/setup/user-info.js | 8 ++++++++ 11 files changed, 111 insertions(+), 41 deletions(-) create mode 100644 src/metrics.js diff --git a/package.json b/package.json index 37a486654b1..7aa55a28a86 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "scout-client": "http://bin.mongodb.org/js/scout-client/v0.0.5/scout-client-0.0.5.tar.gz", "scout-server": "http://bin.mongodb.org/js/scout-server/v0.0.4/scout-server-0.0.4.tar.gz", "tiny-lr": "^0.1.6", + "untildify": "^2.1.0", "uuid": "^2.0.1", "watch": "^0.16.0" }, diff --git a/src/bugsnag.js b/src/bugsnag.js index 1ab322c99ec..216a9a3c39f 100644 --- a/src/bugsnag.js +++ b/src/bugsnag.js @@ -9,7 +9,6 @@ */ var bugsnag = require('bugsnag-js'); var redact = require('./redact'); -var app = require('ampersand-app'); var _ = require('lodash'); var debug = require('debug')('scout:bugsnag'); @@ -53,3 +52,7 @@ module.exports.listen = function listen(app) { app.bugsnag = bugsnag; }; + +module.exports.trackError = function(err) { + return bugsnag.notifyException(err); +}; diff --git a/src/electron/config.js b/src/electron/config.js index c824d4cd29f..85b2e9b0c50 100644 --- a/src/electron/config.js +++ b/src/electron/config.js @@ -2,12 +2,21 @@ var app = require('app'); var nconf = require('nconf'); var path = require('path'); var pkg = require('../../package.json'); +var untildify = require('untildify'); +var debug = require('debug')('scout:electron:config'); -var CONFIG_FILE = path.resolve(__dirname, '../config.json'); var defaults = { + private: false, + github: { + scope: 'user:email,gist', + // For pointing at a GitHub Enterprise installations + host: 'api.github.com', + // e.g. /api/v3 for some GitHub Enterprise installations + github_path_prefix: null, + protocol: 'https' + }, newrelic: { - license_key: null, app_name: pkg.product_name, use_ssl: true, log_enabled: true, @@ -17,17 +26,27 @@ var defaults = { }; nconf - .file(CONFIG_FILE) + .defaults(defaults) + .file(untildify('~/Dropbox/10gen-scout/config/development.json')) .env('__') .argv() - .use('memory') - .defaults(defaults); + .use('memory'); // Some versions of node leave the colon on the end. nconf.overrides({ newrelic: { - enabled: !!nconf.get('newrelic:license_key') + enabled: !!nconf.get('newrelic:license_key') && !nconf.get('private') + }, + github: { + enabled: !!nconf.get('github:client_secret') && !nconf.get('private') } }); +debug('Features enabled', { + github: nconf.get('github:enabled'), + newrelic: nconf.get('newrelic:enabled') +}); + + + module.exports = nconf; diff --git a/src/electron/github-oauth-flow.js b/src/electron/github-oauth-flow.js index 79a882c3a40..c7b12024dde 100644 --- a/src/electron/github-oauth-flow.js +++ b/src/electron/github-oauth-flow.js @@ -2,15 +2,15 @@ var parseURL = require('url').parse; var request = require('request'); var octonode = require('octonode'); var createWindow = require('./window-manager').create; +var config = require('./config'); var format = require('util').format; var debug = require('debug')('scout:electron:github-oauth-flow'); var client; -function exchangeCodeForToken(opts, code, fn) { - var url = 'https://github.com/login/oauth/access_token' - + '?client_id=' + opts.github_client_id - + '&client_secret=' + opts.github_client_secret - + '&code=' + code; +function exchangeCodeForToken(code, fn) { + var url = format('https://github.com/login/oauth/access_token' + + '?client_id=%s&client_secret=%s&code=%s', + config.get('github:client_id'), config.get('github:client_secret'), code); request.get({ url: url, @@ -46,13 +46,13 @@ function getUser(access_token, done) { }); } -function oauthCallback(opts, url, done) { +function oauthCallback(url, done) { var query = parseURL(url, true).query; debug('oauth callback response', query); if (query.error) { return done(new Error('GitHub auth failed: ' + JSON.stringify(query))); } - exchangeCodeForToken(opts, query.code, function(err, res) { + exchangeCodeForToken(query.code, function(err, res) { if (err) return done(err); getUser(res.access_token, function(err, data) { @@ -64,9 +64,15 @@ function oauthCallback(opts, url, done) { }); } -module.exports = function(opts, done) { +module.exports = function(done) { + if (!config.get('github:enabled')) { + return process.nextTick(function() { + done(new Error('GitHub not enabled in config')); + }); + } var url = format('https://github.com/login/oauth/authorize?client_id=%s&scope=%s', - opts.github_client_id, opts.github_scope); + config.get('github:client_id'), config.get('github:scope')); + debug('redirecting to', url); var _window = createWindow({ url: url, @@ -77,8 +83,12 @@ module.exports = function(opts, done) { }); debug('starting github oauth web flow...'); + var pending = true; var onClose = function() { - done(new Error('GitHub oAuth flow cancelled!')); + if (!pending) return; + var err = new Error('GitHub oAuth flow cancelled'); + err.cancelled = true; + done(err); }; // Handle the user starting the github oauth flow but at // any time they could chose to close the window and cancel. @@ -86,8 +96,8 @@ module.exports = function(opts, done) { _window.webContents.on('did-get-redirect-request', function(event, fromURL, toURL) { debug('github redirected authWindow %s -> %s', event, fromURL, toURL); - oauthCallback(opts, toURL, function(err, res) { - _window.off('close', onClose); + oauthCallback(toURL, function(err, res) { + pending = false; _window.close(); _window = null; done(err, res); diff --git a/src/electron/index.js b/src/electron/index.js index 43090154f04..8a0ce42feda 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -13,14 +13,6 @@ var githubOauthFlow = require('./github-oauth-flow'); var setup = require('./setup'); var debug = require('debug')('scout:electron'); -var settings; -try { - settings = require('../../settings.json'); -} catch (e) { - debug('no settings file found. external services disabled.'); - settings = {}; -} - app.on('window-all-closed', function() { debug('All windows closed. Quitting app.'); app.quit(); @@ -62,7 +54,8 @@ app.on('ready', function() { ipc.on('open-connect-dialog', app.emit.bind(app, 'open-connect-dialog')); ipc.on('open-github-oauth-flow', function(event) { var sender = BrowserWindow.fromWebContents(event.sender); - githubOauthFlow(settings, function(err, user) { + githubOauthFlow(function(err, user) { + debug('Got github oauth response', err, user); if (err) { sender.send('github-oauth-flow-error', err); } else { diff --git a/src/intercom.js b/src/intercom.js index d355a06d968..c5781a9472d 100644 --- a/src/intercom.js +++ b/src/intercom.js @@ -1,5 +1,4 @@ var _ = require('lodash'); -var pkg = require('../package.json'); var app = require('ampersand-app'); var debug = require('debug')('scout:intercom'); @@ -25,9 +24,6 @@ module.exports.update = function() { // @todo (imlucas): Expose to main renderer via IPC so the server can track // whatever events it needs to as well. module.exports.track = function(eventName, data) { - data = _.extend(data || {}, { - 'App Version': pkg.version - }); window.Intercom('trackEvent', eventName, data); }; diff --git a/src/metrics.js b/src/metrics.js new file mode 100644 index 00000000000..2b1cec1c75d --- /dev/null +++ b/src/metrics.js @@ -0,0 +1,18 @@ +var intercom = require('./intercom'); +var bugsnag = require('./bugsnag'); +var pkg = require('../package.json'); +var _ = require('lodash'); +var debug = require('debug')('scout:metrics'); + +module.exports.track = function(eventName, data) { + data = _.extend(data || {}, { + 'App Version': pkg.version + }); + debug('tracking `%s`: %j', eventName, data); + intercom.track(eventName, data); +}; + +module.exports.trackError = function(err) { + debug('tracking error %j', err); + bugsnag.trackError(err); +}; diff --git a/src/setup/connect-github.jade b/src/setup/connect-github.jade index 224c960b107..3742ceb88af 100644 --- a/src/setup/connect-github.jade +++ b/src/setup/connect-github.jade @@ -1,7 +1,11 @@ div.animated + .row: .col-xs-12 + h3.h4 Do you have a GitHub account? .row - .col-xs-12 - h3.h4 Do you have a GitHub account? - a.btn.btn-default - i.fa.fa-fw.fa-github - | Sign In To GitHub + .col-xs-6 + button.btn.btn-primary.btn-lg(data-hook='connect-github') + i.fa.fa-fw.fa-github + | Sign In To GitHub + .col-xs-6 + button.btn.btn-default.btn-lg(data-hook='skip') + | Skip diff --git a/src/setup/connect-github.js b/src/setup/connect-github.js index 82dfd7136d0..b23851d0dc3 100644 --- a/src/setup/connect-github.js +++ b/src/setup/connect-github.js @@ -1,5 +1,8 @@ var View = require('ampersand-view'); var app = require('ampersand-app'); +var metrics = require('../metrics'); + +var debug = require('debug')('scout:setup:connect-github'); module.exports = View.extend({ events: { @@ -9,21 +12,36 @@ module.exports = View.extend({ template: require('./connect-github.jade'), onGithubClicked: function(evt) { evt.preventDefault(); - app.ipc.send('open-github-oauth-flow'); + evt.stopPropagation(); + metrics.track('Connect with GitHub started'); app.ipc.once('github-oauth-flow-error', function(err) { - console.error(err); + if (err.cancelled) { + metrics.track('Connect with GitHub cancelled'); + } else { + metrics.trackError(err); + } this.parent.step++; }.bind(this)); + app.ipc.once('github-oauth-flow-complete', function(data) { + metrics.track('Connect with GitHub complete'); + app.user.set(data); app.user.save(); + this.parent.name = data.name; this.parent.email = data.email; this.parent.step++; }.bind(this)); + + app.ipc.send('open-github-oauth-flow'); + }, + skip: function() { + debug('skipping'); + this.parent.step++; }, onSkipClicked: function(evt) { evt.preventDefault(); - this.parent.step++; + this.skip(); } }); diff --git a/src/setup/index.js b/src/setup/index.js index f0713e1fe37..a22653f9a85 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -6,7 +6,7 @@ var debug = require('debug')('scout:setup'); var stepKlasses = [ require('./welcome'), - // require('./connect-github'), + require('./connect-github'), require('./user-info'), // require('./connect-mongodb'), require('./finished') diff --git a/src/setup/user-info.js b/src/setup/user-info.js index ff40f7c4950..3c0b7f1b018 100644 --- a/src/setup/user-info.js +++ b/src/setup/user-info.js @@ -81,10 +81,18 @@ module.exports = View.extend({ this.nameInput.value = app.user.name; }.bind(this)); + if (app.user.name) { + this.nameInput.value = app.user.name; + } + this.listenTo(app.user, 'change:email', function() { this.emailInput.value = app.user.email; }.bind(this)); + if (app.user.email) { + this.emailInput.value = app.user.email; + } + setTimeout(function() { debug('Focusing on name input'); this.nameInput.focus(); From 6a874965b1d6b6a493683c7c2dade286f0e20189 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Tue, 11 Aug 2015 15:02:34 -0400 Subject: [PATCH 22/30] unify all the configs and disable setup via feature flag so we can merge this into master --- gulpfile.js | 13 +++- src/app.js | 72 ++++++++++++------ src/bugsnag.js | 25 +++---- src/electron/config.js | 160 ++++++++++++++++++++++++++++++++-------- src/electron/index.js | 8 +- src/electron/setup.js | 15 ++-- src/index.jade | 7 +- src/index.js | 3 + src/intercom.js | 10 ++- src/minicharts/index.js | 6 +- 10 files changed, 232 insertions(+), 87 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5165bb80260..779a0ccf7d9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -12,6 +12,7 @@ var shell = require('gulp-shell'); var path = require('path'); var del = require('del'); var spawn = require('child_process').spawn; +var config = require('./src/electron/config'); var notify = require('./tasks/notify'); var pkg = require('./package.json'); @@ -127,7 +128,8 @@ gulp.task('pages', function() { return gulp.src('src/index.jade') .pipe(jade({ locals: { - NODE_ENV: process.env.NODE_ENV + NODE_ENV: process.env.NODE_ENV, + CONFIG: JSON.stringify(config.toJSON()) } })) .on('error', notify('jade')) @@ -147,8 +149,13 @@ gulp.task('copy:images', function() { gulp.task('copy:electron', function() { return merge( - gulp.src(['main.js', 'package.json', 'node_modules/animate.css/animate.css', 'README.md']) - .pipe(gulp.dest('build/')), + gulp.src([ + 'main.js', + 'package.json', + 'config.json', + 'node_modules/animate.css/animate.css', + 'README.md' + ]).pipe(gulp.dest('build/')), gulp.src(['src/electron/*']) .pipe(gulp.dest('build/src/electron')) ); diff --git a/src/app.js b/src/app.js index d0fdeb167e9..de557eeaec7 100644 --- a/src/app.js +++ b/src/app.js @@ -1,13 +1,29 @@ +// Injected into index.html by the gulp build task. +var CONFIG = window.CONFIG; + +// Do most basic setup of app here so we can get bugsnag listening +// for errors as high in the stack as possible. +var _ = require('lodash'); var pkg = require('../package.json'); var app = require('ampersand-app'); +/*eslint no-bitwise:0*/ app.extend({ + // @todo (imlucas): use http://npm.im/osx-release and include platform details + // in event tracking. meta: { 'App Version': pkg.version + }, + config: CONFIG, + /** + * Feature flags. + */ + isFeatureEnabled: function(name) { + return Boolean(~~_.get(CONFIG, name + '.enabled')); } }); -require('./bugsnag').listen(app); -var _ = require('lodash'); +require('./bugsnag'); + var domReady = require('domready'); var qs = require('qs'); var getOrCreateClient = require('scout-client'); @@ -65,11 +81,7 @@ var Application = View.extend({ /** * @see http://learn.humanjavascript.com/react-ampersand/creating-a-router-and-pages */ - router: 'object', - /** - * Enable/Disable features with one global switch - */ - features: 'object' + router: 'object' }, events: { 'click a': 'onLinkClick' @@ -158,10 +170,6 @@ var Statusbar = require('./statusbar'); require('./context-menu-manager'); app.extend({ - client: null, - meta: { - 'App Version': pkg.version - }, openSetupDialog: function() { app.ipc.send('open-setup-dialog'); }, @@ -170,22 +178,18 @@ app.extend({ }, init: function() { state.statusbar = new Statusbar(); - this.connection = new Connection(); - this.connection.use(uri); - this.queryOptions = new QueryOptions(); - this.instance = new MongoDBInstance(); - - // feature flags - this.features = { - querybuilder: true - }; + state.connection = new Connection(); + state.connection.use(uri); + + state.queryOptions = new QueryOptions(); + state.instance = new MongoDBInstance(); + state.user = new User(); state.router = new Router(); this.on('change:ipc', function() { debug('ipc now available!'); }); - this.router = state.router; }, use: function(fn) { fn.call(null, this); @@ -210,10 +214,34 @@ Object.defineProperty(app, 'user', { } }); +Object.defineProperty(app, 'instance', { + get: function() { + return state.instance; + } +}); + +Object.defineProperty(app, 'queryOptions', { + get: function() { + return state.queryOptions; + } +}); + +Object.defineProperty(app, 'connection', { + get: function() { + return state.connection; + } +}); + +Object.defineProperty(app, 'router', { + get: function() { + return state.router; + } +}); + Object.defineProperty(app, 'client', { get: function() { return getOrCreateClient({ - seed: app.connection.uri + seed: state.connection.uri }); } }); diff --git a/src/bugsnag.js b/src/bugsnag.js index 216a9a3c39f..80d2a210a61 100644 --- a/src/bugsnag.js +++ b/src/bugsnag.js @@ -10,10 +10,9 @@ var bugsnag = require('bugsnag-js'); var redact = require('./redact'); var _ = require('lodash'); +var app = require('ampersand-app'); var debug = require('debug')('scout:bugsnag'); -var TOKEN = '0d11ab5f4d97452cc83d3365c21b491c'; - // @todo (imlucas): use mongodb-redact function beforeNotify(d) { d.stacktrace = redact(d.stacktrace); @@ -35,13 +34,10 @@ function beforeNotify(d) { * @todo (imlucas): When first-run branch merged, include user id: * https://github.com/bugsnag/bugsnag-js#user */ -module.exports.listen = function listen(app) { - if (!process.env.NODE_ENV) { - process.env.NODE_ENV = 'development'; - } - +var enabled = _.get(app.get, 'bugsnag.enabled'); +if (!enabled) { _.assign(bugsnag, { - apiKey: TOKEN, + apiKey: _.get(app.config, 'bugsnag.token'), autoNotify: true, releaseStage: process.env.NODE_ENV, notifyReleaseStages: ['production', 'development'], @@ -49,10 +45,13 @@ module.exports.listen = function listen(app) { metaData: app.meta, beforeNotify: beforeNotify }); - app.bugsnag = bugsnag; -}; -module.exports.trackError = function(err) { - return bugsnag.notifyException(err); -}; + module.exports.trackError = function(err) { + return bugsnag.notifyException(err); + }; +} else { + module.exports.trackError = function(err) { + console.log('Error', err); + }; +} diff --git a/src/electron/config.js b/src/electron/config.js index 85b2e9b0c50..91923f08a55 100644 --- a/src/electron/config.js +++ b/src/electron/config.js @@ -1,52 +1,150 @@ -var app = require('app'); +var app; +try { + app = require('app'); +} catch (e) { + app = null; +} var nconf = require('nconf'); var path = require('path'); var pkg = require('../../package.json'); var untildify = require('untildify'); var debug = require('debug')('scout:electron:config'); +var _ = require('lodash'); +var features = {}; +// ## feature.private +// +// *default* `off` +// +// Don't send any data to or use third-party services. I'm +// working in a high security environment and this any data +// related to mongodb or my data in it cannot leave my workstation. +features.private = { + enabled: false +}; -var defaults = { - private: false, - github: { - scope: 'user:email,gist', - // For pointing at a GitHub Enterprise installations - host: 'api.github.com', - // e.g. /api/v3 for some GitHub Enterprise installations - github_path_prefix: null, - protocol: 'https' - }, - newrelic: { - app_name: pkg.product_name, - use_ssl: true, - log_enabled: true, - log_level: 'info', - log_filepath: path.join(app.getPath('temp'), 'newrelic.log') - } +// ## feature: setup +// +// *default* `off` +// +// Whether the app should show the setup wizard when the app starts up +// for the first time. +features.setup = { + enabled: false, + version: '1.0.0' +}; +if (app) { + features.setup.file = path.join(app.getPath('userData'), 'setup.json'); +} + +// ## feature: github +// +// *default* `off` +// +// Use GitHub account to fill out inputs in setup wizard and +// enables sharing via GitHub gists. +features.github = { + scope: 'user:email,gist', + // For pointing at a GitHub Enterprise installations + host: 'api.github.com', + // e.g. /api/v3 for some GitHub Enterprise installations + github_path_prefix: null, + protocol: 'https' }; +// ## feature: newrelic +// +// *default* `off` +// +// Send app metrics and exceptions to our New Relic account. +features.newrelic = { + app_name: pkg.product_name, + use_ssl: true, + log_enabled: true, + log_level: 'info' +}; +if (app) { + features.newrelic.log_filepath = path.join(app.getPath('temp'), 'newrelic.log'); +} + +// ## feature: bugsnag +// +// *default* `off` +features.bugsnag = {}; + +features.querybuilder = { + enabled: true +}; + +features.intercom = { + enabled: false, + app_id: 'p57suhg7' +}; + + nconf - .defaults(defaults) - .file(untildify('~/Dropbox/10gen-scout/config/development.json')) - .env('__') - .argv() - .use('memory'); + .defaults(_.clone(features)) + // Allow setting config values via environment variables, e.g. + // `private__enabled=1 npm start` + .env('__'); + +if (process.env.NODE_ENV === 'development') { + // Use the config json in the project's dropbox for easy + // feature sharing on dev machines. + nconf.file(untildify('~/Dropbox/10gen-scout/config/development.json')); +} + +// Use a bundled `config.json` +// @todo (imlucas): Make `npm run release` write this file from +// environment variables set on evergreen. +nconf.file('bundled', path.resolve(__dirname, '../../config.json')); +nconf.use('memory'); + +/** + * @param {String} name - The feature name to check. + * @return {Boolean} Whether the feature is enabled. + */ +/*eslint no-bitwise:0*/ +var isEnabled = nconf.isFeatureEnabled = function(name) { + return Boolean(~~nconf.get(name + ':enabled')); +}; -// Some versions of node leave the colon on the end. +// Use overrides to disable any features that are missing a dependency +// or should not be enabled if `private` is on. nconf.overrides({ newrelic: { - enabled: !!nconf.get('newrelic:license_key') && !nconf.get('private') + enabled: !!nconf.get('newrelic:license_key') && !isEnabled('private') }, github: { - enabled: !!nconf.get('github:client_secret') && !nconf.get('private') + enabled: !!nconf.get('github:client_secret') && !isEnabled('private') + }, + bugsnag: { + enabled: !!nconf.get('bugsnag:token') && !isEnabled('private') + }, + intercom: { + enabled: isEnabled('intercom') && !isEnabled('private') + }, + setup: { + enabled: isEnabled('setup') && !isEnabled('private') } }); -debug('Features enabled', { - github: nconf.get('github:enabled'), - newrelic: nconf.get('newrelic:enabled') -}); - +var featureNames = _.keys(features); +var featuresEnabledMap = _.chain(featureNames) + .map(function(name) { + return [name, nconf.isFeatureEnabled(name)]; + }) + .zipObject() + .value(); +nconf.toJSON = function() { + return _.chain(featureNames) + .map(function(name) { + return [name, nconf.get(name)]; + }) + .zipObject() + .value(); +}; +debug('Config ready! Features enabled: ', featuresEnabledMap); module.exports = nconf; diff --git a/src/electron/index.js b/src/electron/index.js index 8a0ce42feda..c76eba0ad9a 100644 --- a/src/electron/index.js +++ b/src/electron/index.js @@ -8,6 +8,7 @@ var app = require('app'); var BrowserWindow = require('browser-window'); var Menu = require('menu'); var ipc = require('ipc'); +var config = require('./config'); var windows = require('./window-manager'); var githubOauthFlow = require('./github-oauth-flow'); var setup = require('./setup'); @@ -69,6 +70,9 @@ app.on('ready', function() { debug('setup marked complete'); }); }); - - setup(); + if (config.isFeatureEnabled('setup')) { + setup(); + } else { + windows.openConnectDialog(); + } }); diff --git a/src/electron/setup.js b/src/electron/setup.js index 24306f6c10f..1350ecda951 100644 --- a/src/electron/setup.js +++ b/src/electron/setup.js @@ -1,18 +1,15 @@ var fs = require('fs'); -var path = require('path'); var app = require('app'); +var config = require('./config'); var debug = require('debug')('scout:electron:setup'); -var SETUP_PATH = path.join(app.getPath('userData'), 'setup-completed.json'); -var SETUP_VERSION = '1.0.0'; - module.exports = function showSetupOrStart() { - fs.exists(SETUP_PATH, function(exists) { + fs.exists(config.get('setup:file'), function(exists) { if (!exists) { debug('no setup-completed.json yet'); return app.emit('open-setup-dialog'); } - fs.readFile(SETUP_PATH, function(err, buf) { + fs.readFile(config.get('setup:file'), function(err, buf) { if (err) return console.log(err); var d; @@ -22,7 +19,7 @@ module.exports = function showSetupOrStart() { return console.log(err); } debug('user completed setup version %s at %s', d.version, d.completed_at); - if (d.version !== SETUP_VERSION) { + if (d.version !== config.get('setup:version')) { debug('new setup version available so showing setup again.'); return app.emit('open-setup-dialog'); } @@ -34,8 +31,8 @@ module.exports = function showSetupOrStart() { module.exports.markComplete = function(done) { var data = { - version: SETUP_VERSION, + version: config.get('setup:version'), completed_at: new Date() }; - fs.writeFile(SETUP_PATH, JSON.stringify(data), done); + fs.writeFile(config.get('setup:file'), JSON.stringify(data), done); }; diff --git a/src/index.jade b/src/index.jade index 3b1403388f4..012e8398e3d 100644 --- a/src/index.jade +++ b/src/index.jade @@ -2,7 +2,7 @@ doctype html html(lang='en') head title MongoDB - meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'self' http://localhost:35729 https://widget.intercom.io https://js.intercomcdn.com/; style-src 'self' 'unsafe-inline';") + meta(http-equiv="Content-Security-Policy", content="default-src *; script-src 'unsafe-inline' 'self' http://localhost:35729 https://widget.intercom.io https://js.intercomcdn.com/; style-src 'self' 'unsafe-inline';") meta(name='viewport', content='initial-scale=1') link(rel='stylesheet', href='index.css', charset='UTF-8') @@ -15,6 +15,9 @@ html(lang='en') //- Include the livereload client so pages reload automatically when changed. script(src='http://localhost:35729/livereload.js', charset='UTF-8') script(src='src/electron/development-debug-header.js', charset='UTF-8') - + + script(type='text/javascript', charset='UTF-8'). + window.CONFIG = !{CONFIG} + script(src='index.js', charset='UTF-8') script(src='src/electron/inject-app-bindings.js', charset='UTF-8') diff --git a/src/index.js b/src/index.js index 5f032383596..6aa94de360e 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,9 @@ * The main entrypoint for the application! * @see ./app.js */ +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = 'development'; +} /** * @todo (imlucas): Can be removed? */ diff --git a/src/intercom.js b/src/intercom.js index c5781a9472d..7077b2da663 100644 --- a/src/intercom.js +++ b/src/intercom.js @@ -19,17 +19,16 @@ module.exports.update = function() { window.Intercom('update'); }; -// @todo (imlucas): use http://npm.im/osx-release and include platform details -// in event tracking. // @todo (imlucas): Expose to main renderer via IPC so the server can track // whatever events it needs to as well. module.exports.track = function(eventName, data) { + if (!app.isFeatureEnabled('intercom')) return; window.Intercom('trackEvent', eventName, data); }; function boot() { var config = _.extend(app.user.toJSON(), { - app_id: 'p57suhg7' + app_id: _.get(app.config, 'intercom.app_id') }); config.user_id = app.user.id; debug('Syncing user info w/ intercom', config); @@ -64,6 +63,11 @@ module.exports.hide = function() { * @param {models.User} user - The current user. */ module.exports.inject = function(user) { + if (!app.isFeatureEnabled('intercom')) { + debug('intercom is not enabled'); + return; + } + var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; diff --git a/src/minicharts/index.js b/src/minicharts/index.js index 31a5c088e32..d46763a71a6 100644 --- a/src/minicharts/index.js +++ b/src/minicharts/index.js @@ -24,7 +24,9 @@ module.exports = AmpersandView.extend(QueryBuilderMixin, { }, selectedValues: { type: 'array', - default: function() { return []; } + default: function() { + return []; + } } }, initialize: function(opts) { @@ -50,7 +52,7 @@ module.exports = AmpersandView.extend(QueryBuilderMixin, { } else { this.subview = new VizView(this.viewOptions); } - if (app.features.querybuilder) { + if (app.isFeatureEnabled('querybuilder')) { this.listenTo(this.subview, 'querybuilder', this.handleQueryBuilderEvent); } raf(function() { From cbb2e2a24b80f3fd8920a04d2d4dd9c0569dc87d Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 12 Aug 2015 06:06:05 -0400 Subject: [PATCH 23/30] lint+fmt cleanup --- package.json | 4 +++- src/bugsnag.js | 1 + src/electron/setup.js | 1 + src/field-list/index.js | 22 ++++++++++++++++------ src/minicharts/d3fns/date.js | 19 ++++++++++--------- src/minicharts/d3fns/few.js | 1 - src/minicharts/d3fns/many.js | 1 - src/minicharts/querybuilder.js | 13 +++++++++---- src/models/query-options.js | 4 +++- src/refine-view/index.js | 4 +++- src/router.js | 4 ++-- src/setup/connect-mongodb.js | 1 - 12 files changed, 48 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 7aa55a28a86..005c377b37b 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "font-awesome", "octicons", "app", + "ipc", "auto-updater", "crash-reporter", "browser-window", "menu", "jade", - "ampersand-state" + "ampersand-state", + "animate.css" ] }, "fonts": [ diff --git a/src/bugsnag.js b/src/bugsnag.js index 80d2a210a61..b39e70eb75e 100644 --- a/src/bugsnag.js +++ b/src/bugsnag.js @@ -51,6 +51,7 @@ if (!enabled) { return bugsnag.notifyException(err); }; } else { + /*eslint no-console:0*/ module.exports.trackError = function(err) { console.log('Error', err); }; diff --git a/src/electron/setup.js b/src/electron/setup.js index 1350ecda951..d8371525ed4 100644 --- a/src/electron/setup.js +++ b/src/electron/setup.js @@ -3,6 +3,7 @@ var app = require('app'); var config = require('./config'); var debug = require('debug')('scout:electron:setup'); +/*eslint no-console:0*/ module.exports = function showSetupOrStart() { fs.exists(config.get('setup:file'), function(exists) { if (!exists) { diff --git a/src/field-list/index.js b/src/field-list/index.js index 03305968958..308124f1ff2 100644 --- a/src/field-list/index.js +++ b/src/field-list/index.js @@ -90,7 +90,9 @@ var FieldView = View.extend({ el: el, parent: this, collection: this.model.fields - }), { silent: true }); + }), { + silent: true + }); this.listenTo(this.fieldListView, 'change:refineQuery', this.onRefineClause); return this.fieldListView; } @@ -103,7 +105,9 @@ var FieldView = View.extend({ el: el, parent: this, collection: this.model.arrayFields - }), { silent: true }); + }), { + silent: true + }); this.listenTo(this.arrayFieldListView, 'change:refineQuery', this.onRefineClause); return this.arrayFieldListView; } @@ -117,7 +121,7 @@ var FieldView = View.extend({ this.renderWithTemplate(this); this.viewSwitcher = new ViewSwitcher(this.queryByHook('minichart-container')); }, - onRefineClause: function(who, what) { + onRefineClause: function(who) { if (who.getType() === 'MinichartView') { this.refineClause.value = who.refineValue; } @@ -133,12 +137,16 @@ var FieldView = View.extend({ var clauses = []; if (this.fieldListView) { this.fieldListView.refineQuery.clauses.each(function(clause) { - if (clause.valid) clauses.push(this.prefixClauseKey(clause)); + if (clause.valid) { + clauses.push(this.prefixClauseKey(clause)); + } }.bind(this)); } if (this.arrayFieldListView) { this.arrayFieldListView.refineQuery.clauses.each(function(clause) { - if (clause.valid) clauses.push(this.prefixClauseKey(clause)); + if (clause.valid) { + clauses.push(this.prefixClauseKey(clause)); + } }.bind(this)); } if (this.refineClause.valid) { @@ -174,7 +182,9 @@ FieldListView = View.extend({ refineQuery: { type: 'state', required: true, - default: function() { return new Query(); } + default: function() { + return new Query(); + } }, queryContext: 'object' }, diff --git a/src/minicharts/d3fns/date.js b/src/minicharts/d3fns/date.js index fa18bcc8de3..6cbfff1361d 100644 --- a/src/minicharts/d3fns/date.js +++ b/src/minicharts/d3fns/date.js @@ -4,7 +4,6 @@ var moment = require('moment'); var shared = require('./shared'); var many = require('./many'); var raf = require('raf'); -var debug = require('debug')('scout:minicharts:date'); require('../d3-tip')(d3); @@ -59,7 +58,9 @@ var minicharts_d3fns_date = function(opts) { var barcodeBottom = Math.floor(height - 10); var barcodeX = d3.time.scale() - .domain(d3.extent(values, function(d) { return d.ts; })) + .domain(d3.extent(values, function(d) { + return d.ts; + })) .range([0, width]); var upperBarBottom = height / 2 - 20; @@ -155,14 +156,14 @@ var minicharts_d3fns_date = function(opts) { raf(function() { many(weekdays, opts.view, weekdayContainer, width / (upperRatio + 1) - upperMargin, upperBarBottom, { - bgbars: true, - labels: { - 'text-anchor': 'middle', - text: function(d) { - return d.label[0]; + bgbars: true, + labels: { + 'text-anchor': 'middle', + text: function(d) { + return d.label[0]; + } } - } - }); + }); }); // calendar icon diff --git a/src/minicharts/d3fns/few.js b/src/minicharts/d3fns/few.js index c3533d231e4..8125f4d6149 100644 --- a/src/minicharts/d3fns/few.js +++ b/src/minicharts/d3fns/few.js @@ -3,7 +3,6 @@ var _ = require('lodash'); var $ = require('jquery'); var tooltipHtml = require('./tooltip.jade'); var shared = require('./shared'); -var debug = require('debug')('scout:minicharts:few'); require('../d3-tip')(d3); diff --git a/src/minicharts/d3fns/many.js b/src/minicharts/d3fns/many.js index c3833a99e72..d0b38f02cb9 100644 --- a/src/minicharts/d3fns/many.js +++ b/src/minicharts/d3fns/many.js @@ -2,7 +2,6 @@ var d3 = require('d3'); var _ = require('lodash'); var tooltipHtml = require('./tooltip.jade'); var shared = require('./shared'); -var debug = require('debug')('scout:minicharts:many'); require('../d3-tip')(d3); diff --git a/src/minicharts/querybuilder.js b/src/minicharts/querybuilder.js index c8b1ae0075b..6ec6745a7d8 100644 --- a/src/minicharts/querybuilder.js +++ b/src/minicharts/querybuilder.js @@ -1,5 +1,4 @@ var _ = require('lodash'); -var debug = require('debug')('scout:minicharts:index'); var d3 = require('d3'); var LeafValue = require('mongodb-language-model').LeafValue; @@ -32,7 +31,9 @@ module.exports = { this.selectedValues = [data]; } } else if (_.contains(_.pluck(this.selectedValues, 'i'), data.i)) { - _.remove(this.selectedValues, function(d) { return d.i === data.i; }); + _.remove(this.selectedValues, function(d) { + return d.i === data.i; + }); } else { this.selectedValues.push(data); } @@ -67,7 +68,9 @@ module.exports = { $in: this.selectedValues.map(function(el) { return el.d.value; }) - }, { parse: true }); + }, { + parse: true + }); } }, @@ -129,7 +132,9 @@ module.exports = { upper += last.d.dx; } if (lower === upper) { - this.refineValue = new LeafValue({ content: lower }); + this.refineValue = new LeafValue({ + content: lower + }); } else { this.refineValue = new Range(lower, upper, upperInclusive); } diff --git a/src/models/query-options.js b/src/models/query-options.js index bf18fcdaedd..03cee53f272 100644 --- a/src/models/query-options.js +++ b/src/models/query-options.js @@ -3,7 +3,9 @@ var EJSON = require('mongodb-extended-json'); var Query = require('mongodb-language-model').Query; // var debug = require('debug')('scout:models:query-options'); -var DEFAULT_QUERY = new Query({}, { parse: true }); +var DEFAULT_QUERY = new Query({}, { + parse: true +}); var DEFAULT_SORT = { $natural: -1 }; diff --git a/src/refine-view/index.js b/src/refine-view/index.js index 6a00fab14bf..f93b1619b39 100644 --- a/src/refine-view/index.js +++ b/src/refine-view/index.js @@ -70,7 +70,9 @@ module.exports = AmpersandView.extend({ }, buttonClicked: function() { var queryStr = this._cleanupInput(this.queryByHook('refine-input').value); - var queryObj = new Query(EJSON.parse(queryStr), { parse: true }); + var queryObj = new Query(EJSON.parse(queryStr), { + parse: true + }); this.model.query = queryObj; this.trigger('submit', this); }, diff --git a/src/router.js b/src/router.js index ea125845567..f02fc3e3221 100644 --- a/src/router.js +++ b/src/router.js @@ -10,10 +10,10 @@ module.exports = AmpersandRouter.extend({ '': 'index', schema: 'index', connect: 'connect', - 'setup': 'setup', + setup: 'setup', 'setup/:step': 'setup', 'schema/:ns': 'schema', - '(*path)': 'catchAll', + '(*path)': 'catchAll' }, index: function() { intercom.track('Connected to MongoDB'); diff --git a/src/setup/connect-mongodb.js b/src/setup/connect-mongodb.js index 8502dd62abd..532f4decf28 100644 --- a/src/setup/connect-mongodb.js +++ b/src/setup/connect-mongodb.js @@ -5,7 +5,6 @@ module.exports = View.extend({ }, template: require('./connect-mongodb.jade'), onContinueClicked: function(evt) { - debugger; evt.preventDefault(); this.parent.step++; } From e29c6a14c31353d4571e8055bc36bd0c04f2ca08 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 12 Aug 2015 18:20:18 -0400 Subject: [PATCH 24/30] Dont require newrelic if notenabled or their config class pukes --- src/electron/newrelic.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/electron/newrelic.js b/src/electron/newrelic.js index 9764bbe7391..1584797bc32 100644 --- a/src/electron/newrelic.js +++ b/src/electron/newrelic.js @@ -23,8 +23,7 @@ _.assign(process.env, ENV); if (config.get('newrelic:enabled')) { debug('newrelic enabled! view log file at `%s`', config.get('newrelic:log_filepath')); + require('newrelic'); } else { debug('newrelic not enabled'); } - -require('newrelic'); From 93055eb4d39a14fdb25569d67c697eb82b6675c3 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Wed, 12 Aug 2015 18:32:22 -0400 Subject: [PATCH 25/30] Set NEW_RELIC_NO_CONFIG_FILE env var so it doesn't barf that it cant find a config file. --- src/electron/newrelic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/electron/newrelic.js b/src/electron/newrelic.js index 1584797bc32..f9bcdbfc946 100644 --- a/src/electron/newrelic.js +++ b/src/electron/newrelic.js @@ -16,7 +16,8 @@ var ENV = { NEW_RELIC_LOG: config.get('newrelic:log_filepath'), NEW_RELIC_SLOW_SQL_ENABLED: false, NEW_RELIC_UTILIZATION_DETECT_AWS: false, - NEW_RELIC_UTILIZATION_DETECT_DOCKER: false + NEW_RELIC_UTILIZATION_DETECT_DOCKER: false, + NEW_RELIC_NO_CONFIG_FILE: true }; _.assign(process.env, ENV); From 90cdfb7e96f9e504e53d39b83cb48394de169d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4ffner-Gurney?= Date: Tue, 18 Aug 2015 10:47:55 -0400 Subject: [PATCH 26/30] progress on styling onboarding --- images/logo-scout.png | Bin 0 -> 4143 bytes src/electron/window-manager.js | 21 +++------ src/setup/connect-mongodb.jade | 31 ++++++++----- src/setup/index.jade | 4 +- src/setup/index.js | 10 ++--- src/setup/index.less | 29 ++++++++++++ src/setup/user-info.jade | 78 ++++++++++++++++++--------------- src/setup/welcome.jade | 18 ++++---- src/setup/welcome.js | 48 +++++++++++++++++--- 9 files changed, 156 insertions(+), 83 deletions(-) create mode 100644 images/logo-scout.png diff --git a/images/logo-scout.png b/images/logo-scout.png new file mode 100644 index 0000000000000000000000000000000000000000..9bbc63e39f0b8340caba9e8c4b33919763eff29d GIT binary patch literal 4143 zcmV+~5YX?5P)(Md!>RCwC#U0rM=R~5eFWV4&LOI-;G zB#?IUh=dgH1L8+QYr{jk+pX5wwp48qYYPdast}upJ|KY;@q&azoG1PYaU^H~RZXH+ z+ZNh-B6#2hI+b{Whn-5iR6Nv0fYfdAaF27YeSCfI{Ma+uY;wNUD4X$k=FYk2e)l`) z{zM1{gTXk;_~9SF>wf2z)f!uZ!C<68VVW<6VZVCwgPScjAA`Y2gSoU{_`dg4*>Rj7 z{p-K~{^+@TKlbil_x|=LHY0<<$Xwxk@3m&FANIRa;S@s=!l~9HTMv}jj0^@NQ^INe z{z)L4tuT~S6CTiwJc{y1_mN**X3}9W7}*fcw_pDHfNX$b`jTw=^5^2I(~!)814w5} zl1`D$$zU*MBAlnLf3+m_%*KFd2Fj)nNn3M_WLOo3CUTiX7+zyDGZ>6X!V%%} zP^H9#RiT+&CJ!M>SATpThgA#)W1MjMu#7=WCWlR7RfwULMlQDWahXlcU@+##6^=Mq zH{~3VOTi&wm7|AMmN3d3TP?Gx84Sjda2|Z-;(@vm&VgWYMoHkl{C{CUI-qq5L*-*n z+)OYJZ_ss&(0%>J(g+* zwj;6VO5B z77(($Kt6|ZRAgiHk163OHM%a}aVy?hA&`x@EgSlVcFs3k=cDR@ z>uX3HMWTSW6YN2>~sBZPfPL$WzKjFU=IKq?jN@hLV?rObMNX!z@f5G!k} z;?I>R)q>$g1&OI>P=qIsa1=ERN1(#-Wenq!0Q+L$)Vz^ch6KZm!-yw^igtRSbTWE` zXUGL*scuwG$xjj@p`uwo>GeLYaE410DCw9c0gm=Gju1Hv##BlK^F6a7hzZALMUmuX z5}lUnCg;q(NVL98nsQ&^Q0@=|Y#9>g`B!^BkFcgv>lPf><49>p2%QPO@d zlKZ+@lrO6uJ1e`TMBC74vsezJ+hs4~aw|eBrSXrGK14oD8t;Aicm9=M-YXmjx1Det z5j_d8uak<2YQ}!VID8SE1hV>7tk4RcZ=e&pcr7^SCe4In!&?<(v8X&3;C1bUjSikw z6NZr#yw^4~kf?+oHdyr@Wh}FU=UZYlVxg`F2dvwM5e@fO^t@$)8QE@S5TFPAE&6sNBq(^c9Dpps7 zu_~{f7}~_L?j*RksV9b!i}Nu=R>J$cNXDuGRq>pfbfySMp=nY^XjfOucc^z&WL1^d zHlv&{BfJ@-POM=KB$?H4Hzu4*q`|6pTghqiAJ`T~!)izr>$4~r^ph>3plH1`@Hx;q z?Z)_+hoS1-Xon5-TU&Blb&~rrMNE2Kyuuj~1MFq&jKV6Sbo5DpB6(Ql$0YaG(Mep* z>B@wmePnL>XJ~3fMfgNEU(tF=Ag7M4caWda`5n->h7+`~*Pw(}Bm@y?61#&0pcBg$ zk{EpcmZ5wuzSmj=*{ES&AM*gJeCzm`NfjuiVsq-Td*SD6VzhDEAc+g=F6U5S+WZQ< z^9>kIx1g6pC^Z%yK_ft*1_u=aP2D95;t1)-p%)o0aR%I=S38_2a;bNOIqVl#sdSb0jr}ls~gk~ zy$+7^$_NBiTONdv4K%DeZGc$5x+cjdMpK~Y!0VO_4G7Azg!fkEtEb3YP<6Dfmk)Yy zLtX@Ia*yViE_v6UPBwVIHAC5s^8VBw9=;{9yMd(C$cc1zvCh}UXxLkk`VDFGH-N;W za$}@kRGoAT&uOEgDvHUSG*$gnOdSK3v2OY9tx6gn*`{Bq~e0*nY1x&MmSbeyI)yP2onO>)5~IL*d2`&T~qRr z zA!(D|)6<;J6sou>r2S?R)iguyqGSmhk|2ppt|iHE-+A^PRi`4-6M$Yaf-G+S9r zER0Jg17XVp@+J4kQ&ep?FsvzBa#o!r_t#EcNzo>@GxFO^38y28qZb!eg;7IKqxm?! z7r*&xN8CERl^_R@R=!i5y9K!mTbYYd4c0Jx9wZ1A^n_tvC}ot>p~PbfHFeU>9fnX_ zCI6{{bw$L2$Aq@?Quy!kks%xzRt<)nZtHpS6~o7Q;_u@Mdvla?MWs?lb( zGCDYRlgT&IO-u(7Vn`(4yREmF7+L(m%(V(luKxQ^v!Y#Tf8}?l3mV? zZ~!}x4IJ1vg;jH5VQk(FN#e-DhquMzG)>|Ptn{dgqn`z)8J{_qRSnIngeZ`VgCinI zlv#&18VtpvdU3iyv6F&GN(rukd+%t^$aF?9+4O7JIJ4OK4@n|G8`e@NBQ-0$r*$4) zl1>2E;T0_enq>W1)e^D>afD)+o{>G_D9I>ECeTN#&h{5k;)t7~5Yw)T6=G?&QmuA8=`Boc>?Rtyo8 z@m*-bv|`%0%Mua{I{+KGp?ccVLYb9}!(6~dgbisOmPmPYdv20@MX4OH>pM6boOU8` zuWD2|@H=1=(~Ns%_o#)+8zO_Pf)j1~`QS5`N{%DCb55anu>I(qaR2iW@#lB`PV+WB zd9%v(=Ij2HjS7s>C6Y719y~BG?G9nvfg>5FbL;mrUWeDxZ6ejPn@YhLmN!^)CEA)N zg}Py?x|;Rb(41%8@Xlp1xsiB}I0{_<7U?bhh=qC^+A}An(re2bI;QOuutB>*TQIBH za2Lzvi_zZ3`mzpqZZk^RAVH~TnYJhGV3-i#=gV4sf=%jK=Y|a1i*6hIu#D=X7G+OT zFFw|XXYrN6cB$G7@|qYeZCEjUw}Ib>&yBkcYR|8$5FGuvh3!o(vNdiYO8gX-bX2en z*66i~_ZfcZ*-NE@aJq8^Af1A^)c=(D?c0A4@AcnTm0o#i3bHW}!$gt|Cs4t)H=?dX zf`XOOJ2CR~ykYUBPCAtROSZ=Rg&?Z_Dq^&RCeXw9)PG6g(O+LKe%AR!*FF95(ud9##p2@GrB8qEY9JW7 zf(*r$QNHgHLR1OAvL0tcQkkvCI4YRR8J~ad2Ww}}+|fFFSJC@?`SA*yF?X^li_wTq zJtCwl8dOk){}3DFsGu;-mrl(uc=8bQ^9%cI!b2e&wHHo1N*bdqZi?|qdaOPfw>RO; z&7bO>I(1r{KE22m%pY`kI(Cf_lFl$!l=l{)3`XvRbLsLE{=$MJoHKW_1@i~Z$y`aG zi0A~ZZ8jg{wkI6Gf>bwmEZ)Ty%pX*tG!o2g9c~{Qw~sS}aoCuj_KT+%7MqKA-qm9h z<_;JVrbDD#xFh}^hD<)@d0cTY9O+_6#mRiekpmBz-Uf*H>lHdC{(KID!Gyz@hIG{Q tc2G0=jAKtEo0!O8Fc=KR$-w^w7yzK&4+{tn@9F>m002ovPDHLkV1gg} Date: Tue, 18 Aug 2015 11:01:48 -0400 Subject: [PATCH 27/30] line-height tweak --- src/setup/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/setup/index.less b/src/setup/index.less index 2608ec997f1..0d2a813bd55 100644 --- a/src/setup/index.less +++ b/src/setup/index.less @@ -53,7 +53,7 @@ } p.lead { font-size: 16px; - line-height: 24px; + line-height: 28px; } } .btn-connect-github { From 154292d8c513add72ccef75a4def7a58095a5a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4ffner-Gurney?= Date: Wed, 19 Aug 2015 17:52:06 -0400 Subject: [PATCH 28/30] small alignment tweaks --- src/document-list/index.less | 10 +++++++--- src/field-list/index.less | 22 +++++++++++++++++----- src/home/index.less | 7 ++++--- src/refine-view/index.less | 2 +- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/document-list/index.less b/src/document-list/index.less index 3d52d28d0fd..8fe1a1c8e06 100644 --- a/src/document-list/index.less +++ b/src/document-list/index.less @@ -82,7 +82,7 @@ ol.document-list { cursor: pointer; display: block; - @caret-width-base: 6px; + @caret-width-base: 4px; .caret { display: inline-block; width: 12px; @@ -103,14 +103,18 @@ ol.document-list { } ol.document-property-body { - margin-left: 5px; - border-left: 1px dotted @gray5; + // margin-left: 21px; + padding-left: 36px; + // border-left: 1px dotted @gray6; display: none; } &.expanded { > .document-property-header { > .caret { .caret-down; + vertical-align: bottom; + margin-left: 4px; + margin-right: 8px; } } > ol.document-property-body { diff --git a/src/field-list/index.less b/src/field-list/index.less index 999caf4fe2b..e8c574aaf22 100644 --- a/src/field-list/index.less +++ b/src/field-list/index.less @@ -14,7 +14,9 @@ .schema-field-list { // second level - .schema-field-name, + .schema-field-name { + margin-left: 36px; + } .schema-field-type-list, hr.field-divider { margin-left: 48px; @@ -22,7 +24,9 @@ .schema-field-list { // third level - .schema-field-name, + .schema-field-name { + margin-left: 84px; + } .schema-field-type-list, hr.field-divider { margin-left: 96px; @@ -30,7 +34,9 @@ .schema-field-list { // fourth level - .schema-field-name, + .schema-field-name { + margin-left: 132px; + } .schema-field-type-list, hr.field-divider { margin-left: 144px; @@ -46,6 +52,8 @@ text-overflow: ellipsis; white-space: nowrap; display: inline-block; + padding-left: 12px; + margin-left: -12px; } &.schema-field-basic { @@ -61,7 +69,9 @@ &.expanded { .caret { .caret-down; - margin-right: 6px; + margin-right: 4px; + margin-left: -12px; + cursor: pointer; } > .schema-field-list { display: block; @@ -70,7 +80,9 @@ &.collapsed { .caret { .caret-right; - margin-right: 10px; + margin-right: 8px; + margin-left: -12px; + cursor: pointer; } > .schema-field-list { diff --git a/src/home/index.less b/src/home/index.less index 6ecd89e0f2f..07a467293eb 100644 --- a/src/home/index.less +++ b/src/home/index.less @@ -60,7 +60,7 @@ .collection-view { header { - padding: 12px 20px; + padding: 12px 25px; position: relative; z-index: 1; } @@ -85,7 +85,7 @@ } .main { - padding: 0 0 0 20px; + padding: 0 0 0 25px; flex: 1; } @@ -124,9 +124,10 @@ .column-container.sidebar-open { .side { width: 33%; + right: 0; } .splitter { - right: -15px; + right: -2px; } .splitter-button-open { display: none; diff --git a/src/refine-view/index.less b/src/refine-view/index.less index da814970238..41b3b44b4d1 100644 --- a/src/refine-view/index.less +++ b/src/refine-view/index.less @@ -1,6 +1,6 @@ .refine-view-container { background: @gray8; - padding: 12px 20px; + padding: 12px 25px; position: relative; z-index: 1; From 82d1c81c76ee46d27048555b2bac820b5c271687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20Sch=C3=A4ffner-Gurney?= Date: Thu, 27 Aug 2015 10:05:48 -0400 Subject: [PATCH 29/30] update style of primary github login button --- src/setup/index.less | 4 ++-- src/setup/welcome.jade | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/setup/index.less b/src/setup/index.less index 0d2a813bd55..c926aa772ca 100644 --- a/src/setup/index.less +++ b/src/setup/index.less @@ -56,8 +56,8 @@ line-height: 28px; } } -.btn-connect-github { - padding: 18px 24px; +.btn-lg.btn-info.btn-connect-github { + padding: 15px 30px; } .wizard-form-content { margin: 50px 0; diff --git a/src/setup/welcome.jade b/src/setup/welcome.jade index 83b315c739b..151a358b6ea 100644 --- a/src/setup/welcome.jade +++ b/src/setup/welcome.jade @@ -3,7 +3,7 @@ div.animated.wizard-content h1 Welcome to MongoDB Scout p.lead MongoDB Scout provides users with a graphical view of their MongoDB schema by randomly sampling a subset of documents from the entire collection. By sampling a subset of documents, MongoDB Scout has minimal impact on the database and can produce results to the user almost instantly. .wizard-form-content.text-center - button.btn.btn-primary.btn-lg.btn-connect-github(data-hook='connect-github') + button.btn.btn-info.btn-lg.btn-connect-github(data-hook='connect-github') i.fa.fa-fw.fa-github | Sign In With GitHub .wizard-stepper From d2e2248b6433005490b4f855fc91bdfd697cd8d0 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Sun, 30 Aug 2015 21:18:57 -0400 Subject: [PATCH 30/30] Wire up connect mongodb to actually open the app. --- src/setup/connect-github.jade | 11 -------- src/setup/connect-github.js | 47 ----------------------------------- src/setup/connect-mongodb.js | 12 +++++++-- src/setup/finished.jade | 3 --- src/setup/finished.js | 11 -------- src/setup/index.js | 24 +++++++++++++++--- 6 files changed, 31 insertions(+), 77 deletions(-) delete mode 100644 src/setup/connect-github.jade delete mode 100644 src/setup/connect-github.js delete mode 100644 src/setup/finished.jade delete mode 100644 src/setup/finished.js diff --git a/src/setup/connect-github.jade b/src/setup/connect-github.jade deleted file mode 100644 index 3742ceb88af..00000000000 --- a/src/setup/connect-github.jade +++ /dev/null @@ -1,11 +0,0 @@ -div.animated - .row: .col-xs-12 - h3.h4 Do you have a GitHub account? - .row - .col-xs-6 - button.btn.btn-primary.btn-lg(data-hook='connect-github') - i.fa.fa-fw.fa-github - | Sign In To GitHub - .col-xs-6 - button.btn.btn-default.btn-lg(data-hook='skip') - | Skip diff --git a/src/setup/connect-github.js b/src/setup/connect-github.js deleted file mode 100644 index b23851d0dc3..00000000000 --- a/src/setup/connect-github.js +++ /dev/null @@ -1,47 +0,0 @@ -var View = require('ampersand-view'); -var app = require('ampersand-app'); -var metrics = require('../metrics'); - -var debug = require('debug')('scout:setup:connect-github'); - -module.exports = View.extend({ - events: { - 'click [data-hook=connect-github]': 'onGithubClicked', - 'click [data-hook=skip]': 'onSkipClicked' - }, - template: require('./connect-github.jade'), - onGithubClicked: function(evt) { - evt.preventDefault(); - evt.stopPropagation(); - metrics.track('Connect with GitHub started'); - app.ipc.once('github-oauth-flow-error', function(err) { - if (err.cancelled) { - metrics.track('Connect with GitHub cancelled'); - } else { - metrics.trackError(err); - } - this.parent.step++; - }.bind(this)); - - app.ipc.once('github-oauth-flow-complete', function(data) { - metrics.track('Connect with GitHub complete'); - - app.user.set(data); - app.user.save(); - - this.parent.name = data.name; - this.parent.email = data.email; - this.parent.step++; - }.bind(this)); - - app.ipc.send('open-github-oauth-flow'); - }, - skip: function() { - debug('skipping'); - this.parent.step++; - }, - onSkipClicked: function(evt) { - evt.preventDefault(); - this.skip(); - } -}); diff --git a/src/setup/connect-mongodb.js b/src/setup/connect-mongodb.js index 532f4decf28..f8d7df30fe9 100644 --- a/src/setup/connect-mongodb.js +++ b/src/setup/connect-mongodb.js @@ -1,11 +1,19 @@ var View = require('ampersand-view'); +var debug = require('debug')('scout:setup:connect-mongodb'); + module.exports = View.extend({ events: { - 'click [data-hook=continue]': 'onContinueClicked' + 'click [data-hook=continue]': 'onSubmit' }, template: require('./connect-mongodb.jade'), - onContinueClicked: function(evt) { + onSubmit: function(evt) { evt.preventDefault(); + this.parent.set({ + hostname: this.query('input[name=hostname]').value || 'localhost', + port: parseInt(this.query('input[name=port]').value || 27017, 10), + connection_name: this.query('input[name=name]').value || 'Local' + }); + this.parent.step++; } }); diff --git a/src/setup/finished.jade b/src/setup/finished.jade deleted file mode 100644 index 59f68a7c44a..00000000000 --- a/src/setup/finished.jade +++ /dev/null @@ -1,3 +0,0 @@ -.animated - h1 Finished! - button.btn.btn-primary.btn-lg(data-hook='continue') Connect to MongoDB diff --git a/src/setup/finished.js b/src/setup/finished.js deleted file mode 100644 index 13afa5643d2..00000000000 --- a/src/setup/finished.js +++ /dev/null @@ -1,11 +0,0 @@ -var View = require('ampersand-view'); -module.exports = View.extend({ - events: { - 'click [data-hook=continue]': 'onContinueClicked' - }, - template: require('./finished.jade'), - onContinueClicked: function(evt) { - evt.preventDefault(); - this.parent.complete(); - } -}); diff --git a/src/setup/index.js b/src/setup/index.js index 7291fa512dc..668ca7e139a 100644 --- a/src/setup/index.js +++ b/src/setup/index.js @@ -3,13 +3,14 @@ var ViewSwitcher = require('ampersand-view-switcher'); var onAnimationEnd = require('animationend'); var app = require('ampersand-app'); var debug = require('debug')('scout:setup'); +var format = require('util').format; + +var Connection = require('../models/connection'); var stepKlasses = [ require('./welcome'), - // require('./connect-github'), require('./user-info'), require('./connect-mongodb') - // require('./finished') ]; var FirstRunView = View.extend({ @@ -31,10 +32,19 @@ var FirstRunView = View.extend({ port: { type: 'number', default: 27017 + }, + connection_name: { + type: 'string', + default: 'Dev Database' } + }, goToStep: function(n) { debug('going to step %d', n); + var isLast = n === this.steps.length - 1; + if (isLast) { + return this.complete(); + } this.switcher.set(this.steps[n - 1]); app.navigate('setup/' + n, { silent: true @@ -71,8 +81,16 @@ var FirstRunView = View.extend({ document.title = 'Welcome to MongoDB Scout'; }, complete: function() { + debug('Setup complete!'); + var model = new Connection({ + name: this.connection_name, + hostname: this.hostname, + port: this.port + }); + model.save(); + window.open(format('%s?uri=%s#schema', window.location.origin, model.uri)); + app.ipc.send('mark-setup-complete'); - app.ipc.send('open-connect-dialog'); setTimeout(window.close, 500); } });