From 2282473236e3cc06540b280c7d35ded2b92daacd Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 10:44:06 -0400 Subject: [PATCH 01/15] :zap: devtron and react devtools extensions Makes for much simpler debugging or both react components and electron. These devtools extensions are only loaded and activated when Compass is in development mode. ## [react devtools](https://fb.me/react-devtools) > React Developer Tools is a Chrome DevTools extension for the open-source React JavaScript library. It allows you to inspect the React component hierarchies in the Chrome Developer Tools. ![](https://facebook.github.io/react/img/blog/devtools-full.gif) ## [devtron](http://electron.atom.io/devtron/) > Devtron is an open source tool to help you inspect, monitor, and debug your Electron app. Built on top of the amazing Chrome Developer Tools. ![](http://electron.atom.io/images/devtron-ipc.png) --- package.json | 2 ++ src/app/index.js | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/package.json b/package.json index 1fbc1d41055..acc943a1ff0 100644 --- a/package.json +++ b/package.json @@ -160,6 +160,8 @@ "babel-eslint": "^6.0.4", "chai": "^3.4.1", "chai-as-promised": "^5.1.0", + "devtron": "^1.4.0", + "electron-devtools-installer": "^2.0.1", "electron-prebuilt": "1.2.8", "eslint-config-mongodb-js": "^2.2.0", "hadron-build": "^1.0.0-beta.2", diff --git a/src/app/index.js b/src/app/index.js index f68c4179aa1..6f413cebfb4 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -1,6 +1,14 @@ /* eslint no-console:0 */ console.time('app/index.js'); +if (process.env.NODE_ENV === 'development') { + require('devtron').install(); + var devtools = require('electron-devtools-installer'); + devtools.default(devtools.REACT_DEVELOPER_TOOLS) + .then((name) => console.log(`Added Extension: ${name}`)) + .catch((err) => console.log('An error occurred trying to install devtools: ', err)); +} + var Environment = require('../environment'); Environment.init(); From 5c68dd9adadc3d47ec8a7b2df40fec50bdd491b4 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 12:28:53 -0400 Subject: [PATCH 02/15] Merge window default dimensions into window-manager --- src/main/config/index.js | 3 --- src/main/config/windows.js | 33 --------------------------- src/main/window-manager.js | 46 ++++++++++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 src/main/config/index.js delete mode 100644 src/main/config/windows.js diff --git a/src/main/config/index.js b/src/main/config/index.js deleted file mode 100644 index 8aed97d121d..00000000000 --- a/src/main/config/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - windows: require('./windows') -}; diff --git a/src/main/config/windows.js b/src/main/config/windows.js deleted file mode 100644 index 9c04726914d..00000000000 --- a/src/main/config/windows.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Constants for window sizes on multiple platforms - */ - -/** -* The outer dimensions to use for new windows. -*/ -exports.DEFAULT_WIDTH = 1280; -exports.DEFAULT_HEIGHT = 800; - -exports.MIN_WIDTH = 1024; - -/** -* The outer window dimensions to use for new dialog -* windows like the connection and setup dialogs. -*/ -exports.DEFAULT_WIDTH_DIALOG = 900; -exports.DEFAULT_HEIGHT_DIALOG = 800; - -exports.MIN_WIDTH_DIALOG = 768; -/** -* Adjust the heights to account for platforms -* that use a single menu bar at the top of the screen. -*/ -if (process.platform === 'linux') { - exports.DEFAULT_HEIGHT_DIALOG -= 30; - exports.DEFAULT_HEIGHT -= 30; -} else if (process.platform === 'darwin') { - exports.DEFAULT_HEIGHT_DIALOG -= 60; - exports.DEFAULT_HEIGHT -= 60; -} - -module.exports = exports; diff --git a/src/main/window-manager.js b/src/main/window-manager.js index 73cdf1b13ae..4852f5a64ee 100644 --- a/src/main/window-manager.js +++ b/src/main/window-manager.js @@ -8,7 +8,7 @@ var BrowserWindow = electron.BrowserWindow; var _ = require('lodash'); var app = electron.app; -var config = require('./config'); + var migrate = require('./migrations'); var debug = require('debug')('mongodb-compass:electron:window-manager'); var dialog = electron.dialog; @@ -20,6 +20,38 @@ var ipc = require('hadron-ipc'); */ var RESOURCES = path.resolve(__dirname, '../app/'); +/** + * Constants for window sizes on multiple platforms + */ + +/** +* The outer dimensions to use for new windows. +*/ +let DEFAULT_WIDTH = 1280; +let DEFAULT_HEIGHT = 800; + +let MIN_WIDTH = 1024; + +/** +* The outer window dimensions to use for new dialog +* windows like the connection and setup dialogs. +*/ +let DEFAULT_WIDTH_DIALOG = 900; +let DEFAULT_HEIGHT_DIALOG = 800; + +let MIN_WIDTH_DIALOG = 768; +/** +* Adjust the heights to account for platforms +* that use a single menu bar at the top of the screen. +*/ +if (process.platform === 'linux') { + DEFAULT_HEIGHT_DIALOG -= 30; + DEFAULT_HEIGHT -= 30; +} else if (process.platform === 'darwin') { + DEFAULT_HEIGHT_DIALOG -= 60; + DEFAULT_HEIGHT -= 60; +} + /** * The app's HTML shell which is the output of `./src/index.html` * created by the `build:pages` gulp task. @@ -84,9 +116,9 @@ function isSingleInstance(_window) { */ module.exports.create = function(opts) { opts = _.defaults(opts || {}, { - width: config.windows.DEFAULT_WIDTH, - height: config.windows.DEFAULT_HEIGHT, - minwidth: config.windows.MIN_WIDTH, + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + minwidth: MIN_WIDTH, url: DEFAULT_URL }); @@ -139,9 +171,9 @@ module.exports.create = function(opts) { function createWindow(opts, url) { opts = _.extend(opts, { - width: config.windows.DEFAULT_WIDTH_DIALOG, - height: config.windows.DEFAULT_HEIGHT_DIALOG, - minwidth: config.windows.MIN_WIDTH_DIALOG, + width: DEFAULT_WIDTH_DIALOG, + height: DEFAULT_HEIGHT_DIALOG, + minwidth: MIN_WIDTH_DIALOG, url: url }); return module.exports.create(opts); From 987f9b09f3b067a4ba893d8a147d7ab59f7c601e Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 12:32:03 -0400 Subject: [PATCH 03/15] :fire: Remove explain-plan v1 Was long ago ported to react. @see ./src/internal-packages/explain --- src/app/explain-plan/index.jade | 58 ----- src/app/explain-plan/index.js | 335 --------------------------- src/app/explain-plan/index.less | 295 ----------------------- src/app/explain-plan/shard-view.jade | 2 - src/app/explain-plan/stage-model.js | 104 --------- src/app/explain-plan/stage-view.jade | 26 --- src/app/explain-plan/stage-view.js | 197 ---------------- src/app/explain-plan/tree-view.jade | 1 - src/app/explain-plan/tree-view.js | 168 -------------- 9 files changed, 1186 deletions(-) delete mode 100644 src/app/explain-plan/index.jade delete mode 100644 src/app/explain-plan/index.js delete mode 100644 src/app/explain-plan/index.less delete mode 100644 src/app/explain-plan/shard-view.jade delete mode 100644 src/app/explain-plan/stage-model.js delete mode 100644 src/app/explain-plan/stage-view.jade delete mode 100644 src/app/explain-plan/stage-view.js delete mode 100644 src/app/explain-plan/tree-view.jade delete mode 100644 src/app/explain-plan/tree-view.js diff --git a/src/app/explain-plan/index.jade b/src/app/explain-plan/index.jade deleted file mode 100644 index 1bc4aec12e5..00000000000 --- a/src/app/explain-plan/index.jade +++ /dev/null @@ -1,58 +0,0 @@ -.explain-container - .flexbox-fix - .column-container.with-refinebar - .column.main - .summary-container - h3 Query Performance Summary - .row - .col-xs-12 - .summary-stats - .stat.nReturned - i.link(data-link='nReturned') - span.stat-label Documents Returned: - span.stat-value(data-hook='n-returned') - .stat - i.link(data-link='totalKeysExamined') - span.stat-label Index Keys Examined: - span.stat-value(data-hook='total-keys-examined') - .stat - i.link(data-link='totalDocsExamined') - span.stat-label Documents Examined: - span.stat-value(data-hook='total-docs-examined') - .summary-stats - .stat - i.link(data-link='executionTimeMillis') - span.stat-label Actual Query Execution Time (ms): - span.stat-value(data-hook='execution-time-millis') - .stat - i.link(data-link='sortStage') - span.stat-label Sorted in Memory: - span.stat-value(data-hook='in-memory-sort') - .stat - i.link(data-link='indexUsed') - span.index-message(data-hook='index-message-container') - i.fa.fa-fw.fa-exclamation-triangle(data-hook='index-message-icon') - span(data-hook='index-message-text') - .index-definition - div(data-hook='link-to-indexes-container') - a.view-all-indexes(href="#", data-hook='indexes-link') View all indexes - div(data-hook='index-definition-container') - div(data-hook='index-definition-subview') - - - .details-container - .row - .col-md-12 - .view-switcher - span View Details As - .btn-group.btn-group-xs(role="group") - button.btn.btn-default(data-hook='tree-button') Visual Tree - button.btn.btn-default(data-hook='json-button') Raw JSON - - .tree-container - div(data-hook='tree-subview') - - .json-container - .panel.panel-default - .panel-body - ol.document-list(data-hook='raw-subview') diff --git a/src/app/explain-plan/index.js b/src/app/explain-plan/index.js deleted file mode 100644 index c61ce51fe39..00000000000 --- a/src/app/explain-plan/index.js +++ /dev/null @@ -1,335 +0,0 @@ -var View = require('ampersand-view'); -var State = require('ampersand-state'); - -var _ = require('lodash'); -var app = require('ampersand-app'); -var ExplainPlanModel = require('mongodb-explain-plan-model'); -var DocumentView = require('../documents/document-list-item'); -var IndexDefinitionView = require('../indexes/index-definition'); -var TreeView = require('./tree-view'); -var StageModel = require('./stage-model'); -var Action = require('hadron-action'); - -var electron = require('electron'); -var shell = electron.shell; - -var debug = require('debug')('mongodb-compass:explain-plan'); - -var DocumentModel = State.extend({ - idAttribute: '_id', - extraProperties: 'allow' -}); - -/** - * View of the entire Explain Plan view - */ -module.exports = View.extend({ - template: require('./index.jade'), - props: { - explainPlan: 'state', - ns: { - type: 'string', - default: '' - }, - visible: { - type: 'boolean', - required: true, - default: false - }, - showExplainTree: { - type: 'boolean', - required: true, - default: false - }, - // refine bar for explain view - hasRefineBar: ['boolean', true, true], - activeDetailView: { - type: 'string', - default: 'tree', - values: ['json', 'tree'] - } - }, - session: { - rawSubview: 'object', - treeSubview: 'object', - indexDefinitionSubview: 'object' - }, - derived: { - usedMultipleIndexes: { - deps: ['explainPlan.usedIndex'], - fn: function() { - if (!this.explainPlan) { - return false; - } - var usedIndex = this.explainPlan.usedIndex; - return _.isArray(usedIndex); - } - }, - indexMessageType: { - deps: ['explainPlan.isCovered', 'explainPlan.isCollectionScan', 'usedMultipleIndexes'], - fn: function() { - if (this.usedMultipleIndexes) { - return 'MULTIPLE'; - } - if (!this.explainPlan) { - return 'UNAVAILABLE'; - } - if (this.explainPlan.isCollectionScan) { - return 'COLLSCAN'; - } - if (this.explainPlan.isCovered) { - return 'COVERED'; - } - return 'INDEX'; - } - }, - showWarningTriangle: { - deps: ['indexMessageType'], - fn: function() { - return (this.indexMessageType === 'COLLSCAN' || this.indexMessageType === 'MULTIPLE'); - } - }, - indexMessage: { - deps: ['indexMessageType'], - fn: function() { - if (this.indexMessageType === 'COLLSCAN') { - return 'No index available for this query.'; - } - if (this.indexMessageType === 'COVERED') { - return 'Query covered by index:'; - } - if (this.indexMessageType === 'MULTIPLE') { - return 'Shard results differ (see details below)'; - } - return 'Query used the following index:'; - } - }, - rawOutput: { - deps: ['explainPlan.rawExplainObject'], - fn: function() { - if (!this.explainPlan) { - return ''; - } - return JSON.stringify(this.explainPlan.rawExplainObject, null, ' '); - } - }, - inMemorySort: { - deps: ['explainPlan.inMemorySort'], - fn: function() { - if (!this.explainPlan) { - return ''; - } - return this.explainPlan.inMemorySort ? 'Yes' : 'No'; - } - }, - showLinkToIndexes: { - deps: ['explainPlan.isCollectionScan', 'usedMultipleIndexes'], - fn: function() { - if (!this.explainPlan) { - return false; - } - return this.explainPlan.isCollectionScan && !this.usedMultipleIndexes; - } - } - }, - events: { - 'click [data-hook=indexes-link]': 'indexLinkClicked', - 'click [data-hook=json-button]': 'jsonButtonClicked', - 'click [data-hook=tree-button]': 'treeButtonClicked', - 'click i.link': 'linkIconClicked' - }, - bindings: { - ns: { - hook: 'ns' - }, - showExplainTree: { - type: 'toggle', - hook: 'tree-button' - }, - visible: { - type: 'booleanClass', - no: 'hidden' - }, - 'explainPlan.usedIndex': { - type: 'attribute', - name: 'title', - hook: 'index-definition-container' - }, - activeDetailView: [ - { - type: 'switch', - cases: { - 'tree': '.tree-container', - 'json': '.json-container' - } - }, - { - type: 'switchClass', - name: 'active', - cases: { - 'tree': '[data-hook=tree-button]', - 'json': '[data-hook=json-button]' - } - } - ], - 'explainPlan.totalKeysExamined': { - hook: 'total-keys-examined' - }, - 'explainPlan.totalDocsExamined': { - hook: 'total-docs-examined' - }, - 'explainPlan.indexUsed': { - hook: 'index-used' - }, - 'explainPlan.sortStage': { - hook: 'sort-stage' - }, - 'showLinkToIndexes': { - type: 'switch', - cases: { - true: '[data-hook=link-to-indexes-container]', - false: '[data-hook=index-name-container]' - } - }, - 'explainPlan.nReturned': { - hook: 'n-returned' - }, - 'explainPlan.executionTimeMillis': { - hook: 'execution-time-millis' - }, - showWarningTriangle: { - type: 'toggle', - hook: 'index-message-icon' - }, - indexMessage: { - hook: 'index-message-text' - }, - indexMessageType: [ - { - type: 'switchAttribute', - hook: 'index-message-text', - name: 'style', - cases: { - 'COLLSCAN': 'color: #7F6A4E;', - 'MULTIPLE': 'color: #7F6A4E;', - 'INDEX': 'color: #313030;', - 'COVERED': 'color: #507b32;' - } - } - ], - 'inMemorySort': { - hook: 'in-memory-sort' - }, - rawOutput: { - hook: 'raw-output' - } - }, - initialize: function() { - this.listenTo(this.model, 'sync', this.onModelSynced.bind(this)); - this.on('change:visible', this.onVisibleChanged.bind(this)); - Action.filterChanged.listen(this.fetchExplainPlan.bind(this)); - this.showExplainTree = app.isFeatureEnabled('showExplainPlanTab'); - }, - onModelSynced: function() { - this.ns = this.model._id; - this.fetchExplainPlan(); - }, - onQueryChanged: function() { - this.fetchExplainPlan(); - }, - indexLinkClicked: function(e) { - e.stopPropagation(); - e.preventDefault(); - this.parent.switchView('indexView'); - }, - jsonButtonClicked: function() { - this.activeDetailView = 'json'; - }, - treeButtonClicked: function() { - this.activeDetailView = 'tree'; - }, - fetchExplainPlan: function(query) { - var filter = query || app.queryOptions.query.serialize(); - var options = {}; - var view = this; - if (this.ns) { - app.dataService.explain(this.ns, filter, options, function(err, explain) { - if (err) { - return debug('error', err); - } - view.explainPlan = new ExplainPlanModel(explain); - - // remove old tree view - if (view.treeSubview) { - view.treeSubview.remove(); - } - // render new tree view - var stageModel = new StageModel(view.explainPlan.rawExplainObject.executionStats.executionStages, { - parse: true - }); - view.treeSubview = view.renderSubview(new TreeView({ - model: stageModel, - parent: view - }), '[data-hook=tree-subview]'); - - // create new document model from raw explain output - var rawDocModel = new DocumentModel(view.explainPlan.rawExplainObject); - // remove old view if present - if (view.rawSubview) { - view.rawSubview.remove(); - } - // render document model with a DocumentView - view.rawSubview = view.renderSubview(new DocumentView({ - model: rawDocModel, - parent: view - }), '[data-hook=raw-subview]'); - // expand all top-level fields in the explain output - var toplevel = 'li.document-list-item > ol > li.document-property.object,' + - 'li.document-list-item > ol > li.document-property.array'; - _.each(view.queryAll(toplevel), function(el) { - el.classList.toggle('expanded'); - }); - - // remove old index definition view first - if (view.indexDefinitionSubview) { - view.indexDefinitionSubview.remove(); - } - - // find index definition model and create view - if (_.isString(view.explainPlan.usedIndex) && !this.usedMultipleIndexes) { - var indexModel = view.model.indexes.get(view.explainPlan.usedIndex, 'name'); - - view.indexDefinitionSubview = view.renderSubview(new IndexDefinitionView({ - model: indexModel, - parent: view - }), '[data-hook=index-definition-subview]'); - } - }); - } - }, - onVisibleChanged: function() { - if (this.visible) { - this.parent.refineBarView.visible = this.hasRefineBar; - } - }, - linkIconClicked: function(event) { - event.preventDefault(); - event.stopPropagation(); - - // maps which info sprinkles go to which website - var dataLink = event.target.dataset.link; - var baseURL = 'https://docs.mongodb.com/manual/reference/explain-results/#explain.executionStats'; - var urlMap = { - totalKeysExamined: baseURL + '.totalKeysExamined', - totalDocsExamined: baseURL + '.totalDocsExamined', - nReturned: baseURL + '.nReturned', - executionTimeMillis: baseURL + '.executionTimeMillis', - indexUsed: 'https://docs.mongodb.com/manual/reference/explain-results/#collection-scan-vs-index-use', - sortStage: 'https://docs.mongodb.com/manual/reference/explain-results/#sort-stage' - }; - var url = _.get(urlMap, dataLink, null); - if (url) { - shell.openExternal(url); - } - } -}); diff --git a/src/app/explain-plan/index.less b/src/app/explain-plan/index.less deleted file mode 100644 index 8bdac96e331..00000000000 --- a/src/app/explain-plan/index.less +++ /dev/null @@ -1,295 +0,0 @@ -.explain-container { - - // hacks for json view on explain tab - ol.document-list li.document-list-item ol.document-property-body li.document-property.array ol.document-property-body .document-property-key, - ol.document-list li.document-list-item ol.document-property-body li.document-property.object ol.document-property-body .document-property-key { - margin-left: 0; - } - - ol.document-list li.document-list-item ol.document-property-body li.document-property.array.expanded > ol.document-property-body, - ol.document-list li.document-list-item ol.document-property-body li.document-property.object.expanded > ol.document-property-body { - margin-left: 16px; - } - - ol.document-list li.document-list-item ol.document-property-body li.document-property.array.expanded, - ol.document-list li.document-list-item ol.document-property-body li.document-property.object.expanded { - margin-left: 0; - } - - ol.document-list li.document-list-item ol.document-property-body li.document-property.array, - ol.document-list li.document-list-item ol.document-property-body li.document-property.object { - margin-left: 0; - } - // end hacks - - .summary-container { - background: @pw; - padding: 12px; - - h3 { - text-transform: capitalize; - font-weight: bold; - font-size: 16px; - margin: 6px 0 12px; - color: @gray1; - } - - .stat { - font-size: 14px; - padding: 6px 0; - border-top: 1px solid @gray7; - } - - .stat .stat-label { - color: @gray4; - } - - .stat .stat-value { - color: @gray0; - font-weight: bold; - margin-left: 6px; - } - - .stat.nReturned .stat-value { - color: @pw; - background: @chart1; - font-weight: bold; - padding: 2px 6px; - font-size: 14px; - line-height: 1; - border-radius: 20px; - } - - .fa-exclamation-triangle { - color: @alertOrange; - margin-right: 5px; - } - - .index-definition { - margin-top: 0; - a.view-all-indexes { - margin-left: 15px; - } - p.definition { - position: static; - margin: 6px 6px 0 5px; - } - } - - .summary-stats { - width: 320px; - float: left; - margin-right: 24px; - } - } - - i.link { - .fa-icon; - margin-right: 5px; - cursor: pointer; - color: @gray6; - font-size: 12px; - - &:hover { - color: @chart1; - } - &:before { - content: @fa-var-info-circle; - } - } - - .view-switcher { - padding: 6px 0; - margin-bottom: 24px; - border-top: 1px solid @gray7; - border-bottom: 1px solid @gray7; - - span { - margin-right: 7px; - text-transform: uppercase; - font-size: 11px; - color: @gray3; - } - - button:focus { - outline-style: none; - } - } - - .stages-container { - position: relative; - overflow: hidden; - - .card { - pointer-events: none; - width: 276px; - background: #fff; - border: 1px solid @gray7; - margin: 0 auto; - position: absolute; - border-radius: 3px; - padding: 18px 12px 12px; - box-shadow: 0px 2px 0px rgba(0,0,0,0.06); - - &:hover { - box-shadow: 0px 4px 2px rgba(0,0,0,0.12); - } - - &.shard { - background: @gray8; - color: @gray4; - border: none; - box-shadow: none; - h3 { - color: @gray4; - text-align: center; - } - } - - h3 { - text-transform: uppercase; - font-weight: bold; - font-size: 14px; - margin: 0; - color: @gray1; - } - - ul { - font-size: 12px; - list-style: none; - margin: 0; - padding: 0; - } - - ul li span.key { - color: @gray4; - } - - ul li span.value { - color: @gray1; - font-weight: bold; - } - - ul li span.key:after { - content: ": "; - } - - ul.core { - margin-bottom: 12px; - } - - ul.core li { - display: inline-block; - margin-top: 9px; - line-height: 1; - } - - ul.core li.nReturned .value { - color: @pw; - background: @chart1; - font-weight: bold; - padding: 2px 6px; - font-size: 12px; - line-height: 1; - border-radius: 20px; - } - - ul.core li.exec-time { - float: right; - margin-right: 18px; - } - - ul.highlighted li { - border-top: 1px solid @gray7; - padding: 11px 0; - } - - .clock { - height: 60px; - width: 60px; - position: absolute; - top: 18px; - right: -31px; - } - - .face { - background: #fff url(images/explain-card-clockface.png) -1px -1px no-repeat; - background-size: 48px 48px; - border: 1px solid @gray6; - border-radius: 24px; - font-size: 10px; - height: 48px; - width: 48px; - position: absolute; - top: 6px; - left: 6px; - text-align: center; - transition: all 250ms ease-out; - } - - .face:hover { - - } - - .face span { - display: block; - font-size: 14px; - font-weight: bold; - margin-top: 11px; - color: @chart1; - } - - ul li span.value .clock .face { - font-weight: normal; - color: #818487; - } - - .details { - border-top: 1px solid @gray7; - padding-top: 12px; - } - - .details button.btn { - pointer-events: all; - margin: 0; - } - - .details-output { - pointer-events: all; - padding: 6px; - margin-top: 6px; - background: @gray8; - border-radius: 3px; - display: none; - } - - .details.open .details-output { - display: block; - } - - .details-output pre { - background: none; - word-break: break-word; - border: none; - margin: 0; - font-size: 11px; - - } - - .details-output pre code { - background: none; - margin: 0; - } - } // .card - - .link { - fill: none; - stroke: @gray6; - stroke-width: 6px; - } - } // .tree-container -} // .explain-container -.flexbox-fix { - height: 1px; - background-color: @gray8; -} diff --git a/src/app/explain-plan/shard-view.jade b/src/app/explain-plan/shard-view.jade deleted file mode 100644 index ebf565ded8b..00000000000 --- a/src/app/explain-plan/shard-view.jade +++ /dev/null @@ -1,2 +0,0 @@ -.card.shard(id='#{model.id}') - h3(data-hook='name') diff --git a/src/app/explain-plan/stage-model.js b/src/app/explain-plan/stage-model.js deleted file mode 100644 index 804731e38f6..00000000000 --- a/src/app/explain-plan/stage-model.js +++ /dev/null @@ -1,104 +0,0 @@ -var State = require('ampersand-model'); -var Collection = require('ampersand-collection'); -var lodashMixin = require('ampersand-collection-lodash-mixin'); -var _ = require('lodash'); - -// var debug = require('debug')('mongodb-compass:explain:stage-model'); - -var Stage; - -var StageCollection = Collection.extend(lodashMixin, { - model: function(attrs, options) { - return new Stage(attrs, options); - }, - isModel: function(model) { - return (model instanceof Stage); - } -}); - -/** - * This class represents the model for each of the stages. - */ -Stage = State.extend({ - idAttribute: 'id', - props: { - id: 'string', - name: 'string', - nReturned: 'number', - totalExecTimeMS: 'number', - curStageExecTimeMS: 'number', - prevStageExecTimeMS: 'number', - details: 'object', - x: 'number', - y: 'number', - xoffset: ['number', true, 0], - yoffset: ['number', true, 0], - depth: 'number' - }, - derived: { - isShard: { - deps: ['details'], - fn: function() { - return !!this.details.shardName; - } - }, - highlightValues: { - deps: ['name', 'details'], - fn: function() { - switch (this.name) { - case 'IXSCAN': return { - 'Index Name': this.details.indexName, - 'Multi Key Index': this.details.isMultiKey - }; - case 'PROJECTION': return { - 'Transform by': JSON.stringify(this.details.transformBy) - }; - default: return {}; - } - } - }, - highlightPairs: { - deps: ['highlightValues'], - fn: function() { - return _.pairs(this.highlightValues); - } - } - }, - collections: { - children: StageCollection - }, - parse: function(attrs) { - var parsed = { - name: attrs.stage || attrs.shardName, - nReturned: attrs.nReturned, - curStageExecTimeMS: attrs.executionTimeMillisEstimate !== undefined ? - attrs.executionTimeMillisEstimate : attrs.executionTimeMillis, - details: _.omit(attrs, ['inputStage', 'inputStages', 'shards', 'executionStages']), - x: attrs.x, - y: attrs.y, - depth: attrs.depth - }; - if (attrs.inputStage) { - parsed.children = [attrs.inputStage]; - } else if (attrs.inputStages || attrs.shards || attrs.executionStages) { - parsed.children = attrs.inputStages || attrs.shards || attrs.executionStages; - } else { - parsed.children = []; - } - return parsed; - }, - serialize: function() { - var attrOpts = {props: true, derived: true}; - var res = this.getAttributes(attrOpts, true); - _.forOwn(this._children, function(value, key) { - res[key] = this[key].serialize(); - }, this); - _.forOwn(this._collections, function(value, key) { - res[key] = this[key].serialize(); - }, this); - return res; - } -}); - -module.exports = Stage; -module.exports.Collection = StageCollection; diff --git a/src/app/explain-plan/stage-view.jade b/src/app/explain-plan/stage-view.jade deleted file mode 100644 index b7d3e5c817a..00000000000 --- a/src/app/explain-plan/stage-view.jade +++ /dev/null @@ -1,26 +0,0 @@ -.card(id='#{model.id}') - h3(data-hook='name') - - ul.core - li.key-value-pair.nReturned - span.key nReturned - span.value(data-hook='n-returned') - li.key-value-pair.exec-time - span.key Execution Time - span.value - .clock - .face - span(data-hook='exec-ms') - | ms - - ul.highlighted - each valuePair in model.highlightPairs - li.key-value-pair - span.key= valuePair[0] - span.value= valuePair[1] - - .details(data-hook='details') - button.btn.btn-xs.btn-default(data-hook='details-button') Details - .details-output - pre - code(data-hook='stage-details') diff --git a/src/app/explain-plan/stage-view.js b/src/app/explain-plan/stage-view.js deleted file mode 100644 index 620eda0a03b..00000000000 --- a/src/app/explain-plan/stage-view.js +++ /dev/null @@ -1,197 +0,0 @@ -var $ = require('jquery'); -var View = require('ampersand-view'); -var d3 = require('d3'); -// var tooltipMixin = require('../tooltip-mixin'); -var _ = require('lodash'); - -var debug = require('debug')('mongodb-compass:explain:stage-view'); - -var stageTemplate = require('./stage-view.jade'); -var shardTemplate = require('./shard-view.jade'); - -var zIndexCounter = 100; - -/** - * This view renders a single stage card. The view positions are determined - * by the d3.layout.flextree() algorithm, but then placed via CSS. The - * connecting lines between cards are SVG, see ./tree-view.js - */ -module.exports = View.extend( /* tooltipMixin, */ { - template: stageTemplate, - props: { - detailsOpen: { - type: 'boolean', - default: false, - required: true - } - }, - derived: { - detailsJSON: { - deps: ['model.details'], - fn: function() { - return JSON.stringify(this.model.details, null, ' '); - } - }, - posx: { - deps: ['model.x', 'model.xoffset'], - fn: function() { - return this.model.x + this.model.xoffset; - } - }, - posy: { - deps: ['model.y', 'model.yoffset'], - fn: function() { - return this.model.y + this.model.yoffset; - } - }, - deltaExecTime: { - deps: ['model.curStageExecTimeMS', 'model.prevStageExecTimeMS'], - fn: function() { - return this.model.curStageExecTimeMS - this.model.prevStageExecTimeMS; - } - } - // clockTooltipMessage: { - // deps: ['deltaExecTime'], - // fn: function() { - // return 'This stage took an estimated\n' + this.deltaExecTime + 'ms to execute.'; - // } - // } - }, - events: { - 'click [data-hook=details] > button.btn': 'detailsClicked' - }, - bindings: { - 'model.name': { - hook: 'name' - }, - 'model.nReturned': { - hook: 'n-returned' - }, - // 'model.curStageExecTimeMS': { - deltaExecTime: { // only show the time THIS stage actually used up. - hook: 'exec-ms' - }, - 'posx': { - type: function(el, value) { - $(el).css('left', value); - } - }, - 'posy': { - type: function(el, value) { - $(el).css('top', value); - } - }, - 'detailsJSON': { - hook: 'stage-details' - }, - detailsOpen: { - type: 'booleanClass', - name: 'open', - hook: 'details' - } - // clockTooltipMessage: { - // selector: '.clock', - // type: function(el) { - // // need to set `title` and `data-original-title` due to bug in bootstrap's tooltip - // // @see https://github.com/twbs/bootstrap/issues/14769 - // this.tooltip({ - // el: el, - // title: this.clockTooltipMessage, - // placement: 'top', - // container: 'body' - // }).attr('data-original-title', this.clockTooltipMessage); - // } - // } - }, - initialize: function() { - if (this.model.isShard) { - this.template = shardTemplate; - } - this.listenTo(this.model, 'change:totalExecTimeMS', this.drawArcs.bind(this)); - }, - render: function() { - this.renderWithTemplate(this); - this.drawArcs(); - }, - detailsClicked: function() { - this.toggle('detailsOpen'); - $(this.query()).css('z-index', this.detailsOpen ? zIndexCounter++ : 'initial'); - $(this.query('.btn')).toggleClass('active'); - this.parent.trigger('resize'); - }, - drawArcs: function() { - // inputs from explain plan stage - var totalExMillis = this.model.totalExecTimeMS; - var curStageExMillis = this.model.curStageExecTimeMS; - var prevStageExMillis = this.model.prevStageExecTimeMS; - - debug(this.model.name, totalExMillis, curStageExMillis, prevStageExMillis); - - // transforms to get the right percentage of arc for each piece of the clock - var curArcStart = ((prevStageExMillis / totalExMillis) * 2 * Math.PI) || 0; - var curArcEnd = ((curStageExMillis / totalExMillis) * 2 * Math.PI) || 0; - - var prevArcStart = 0; - var prevArcEnd = curArcStart; - - var clockWidth = 60; - var clockHeight = 60; - - // An arc function with all values bound except the endAngle. So, to compute an - // SVG path string for a given angle, we pass an object with an endAngle - // property to the `arc` function, and it will return the corresponding string. - var arcInit = d3.svg.arc(); - - // Create the SVG container, and apply a transform such that the origin is the - // center of the canvas. This way, we don't need to position arcs individually. - var svgClock = d3.select(this.query('.clock')).selectAll('svg').data([null]) - .enter().append('svg') - .attr('width', clockWidth) - .attr('height', clockHeight) - .append('g') - .attr('transform', 'translate(' + clockWidth / 2 + ',' + clockHeight / 2 + ')'); - - // Add the prevStageArc arc - var prevStageArc = svgClock.append('path') - .datum({endAngle: prevArcStart, startAngle: prevArcStart, innerRadius: 24, outerRadius: 29}) - // .style('stroke', '#bbb') - .style('fill', '#dfdfdf') - .attr('d', arcInit); - - // Add the curStageArc arc in blue - var curStageArc = svgClock.append('path') - .datum({endAngle: curArcStart, startAngle: curArcStart, innerRadius: 24, outerRadius: 29}) - .style('fill', '#43B1E5') - .attr('d', arcInit); - - - // arctween function taken from http://bl.ocks.org/mbostock/5100636 and adapted for two arcs - var arcTween = function(transition, newEndAngle, newStartAngle) { - transition.attrTween('d', function(d) { - var interpolateEnd = d3.interpolate(d.endAngle, newEndAngle); - var interpolateStart = d3.interpolate(d.endAngle, newStartAngle); - - return function(t) { - d.startAngle = interpolateStart(t); - d.endAngle = interpolateEnd(t); - return arcInit(d); - }; - }); - }; - - // animate arc - var animateArc = function(arcName, arcEnd, prvArcEnd, duration, delay) { - arcName.transition() - .duration(duration) - .delay(delay) - .call(arcTween, arcEnd, prvArcEnd); - }; - - _.defer(function() { - // draw the gray arc - animateArc(prevStageArc, prevArcEnd, 0, 0, 0); - // draw the blue arc - animateArc(curStageArc, curArcEnd, prevArcEnd, 0, 0); - }); - } -}); diff --git a/src/app/explain-plan/tree-view.jade b/src/app/explain-plan/tree-view.jade deleted file mode 100644 index b6e0fc645d7..00000000000 --- a/src/app/explain-plan/tree-view.jade +++ /dev/null @@ -1 +0,0 @@ -.stages-container(data-hook='stages-container') diff --git a/src/app/explain-plan/tree-view.js b/src/app/explain-plan/tree-view.js deleted file mode 100644 index 126b3aa2e86..00000000000 --- a/src/app/explain-plan/tree-view.js +++ /dev/null @@ -1,168 +0,0 @@ -var $ = require('jquery'); -var View = require('ampersand-view'); -var StageView = require('./stage-view'); -var StageCollection = require('./stage-model').Collection; -var _ = require('lodash'); -var format = require('util').format; -var d3 = window.d3 = require('d3'); - -// this plugin allows for tree layout of variable-sized nodes -require('d3-flextree'); - -// var debug = require('debug')('mongodb-compass:explain:tree'); - -var DEFAULT_CARD_WIDTH = 276; // width of a card -var DEFAULT_CARD_HEIGHT = 132; // height of a card without highlighted fields -var SHARD_CARD_HEIGHT = 30; // height of a shard label card -var HIGHLIGHT_FIELD_HEIGHT = 41; // height of a single 'heighlighted' field -var VERTICAL_PADDING = 50; // vertical space between two cards - -module.exports = View.extend({ - template: require('./tree-view.jade'), - props: { - height: 'number', // height of the tree in px - width: 'number' // width of the tree in px - }, - bindings: { - height: { - type: function(el, value) { - $(el).css('height', value); - } - } - }, - collections: { - stages: StageCollection - }, - initialize: function() { - this.on('resize', this.onResize.bind(this)); - }, - onResize: function() { - this.height = Math.max.apply(null, _.map(this.queryAll('.card'), function(card) { - return card.offsetTop + card.offsetHeight + VERTICAL_PADDING; - })); - }, - render: function() { - this.renderWithTemplate(this); - // the cards themselves are divs rendered as AmpersandViews - this.renderCollection(this.stages, StageView, '[data-hook=stages-container]'); - this.drawd3(); - }, - drawd3: function() { - var view = this; - var tree = d3.layout.flextree() - .setNodeSizes(true) - .nodeSize(function(d) { - var height = d.isShard ? SHARD_CARD_HEIGHT : DEFAULT_CARD_HEIGHT + - (d.highlightPairs.length * HIGHLIGHT_FIELD_HEIGHT); - height += VERTICAL_PADDING; - return [DEFAULT_CARD_WIDTH, height]; - }) - .spacing(function separation(a, b) { - return a.parent === b.parent ? 20 : 50; - }); - - var nodes = tree.nodes(view.model.serialize({derived: true})); - var links = tree.links(nodes); - - // compute some boundaries - var leafNode = _.max(nodes, 'depth'); - var rightMostNode = _.max(nodes, 'x'); - var leftMostNode = _.min(nodes, 'x'); - - var xDelta = leftMostNode.x; - view.height = leafNode.y + leafNode.y_size; - view.width = rightMostNode.x + rightMostNode.x_size - leftMostNode.x; - - var totalExecTimeMS = this._computeExecTimes(nodes[0]); - - nodes.forEach(function(d, i) { - // set total exec time for all nodes - d.totalExecTimeMS = totalExecTimeMS; - // align left most node to the left edge - d.x += -xDelta; - // set the id here, so that already existing stage models can be merged - d.id = format('stage-%d', i); - }); - - // merge new with existing stages, renderCollection will automatically - // draw new ones and remove old ones, similar to d3 data bindings. - view.stages.set(nodes); - - // right angle links between nodes - var elbow = function(d) { - return 'M' + (d.source.x + d.source.x_size / 2) + ',' + d.source.y + - 'V' + (d.target.y - VERTICAL_PADDING / 2) + - 'H' + (d.target.x + DEFAULT_CARD_WIDTH / 2) + - 'V' + d.target.y; - }; - - // cards and drag behavior (via d3.behavior.zoom) - var svg = d3.select(view.el).selectAll('svg.links').data([null]); - var container; - var moveTree; - - var zoom = d3.behavior.zoom() - .scaleExtent([1, 1]) - .on('zoom', function() { - var dx = d3.event.translate[0]; - moveTree(dx); - }); - - function pan() { - var TRANSLATE_FACTOR = 4; // determines speed at which to move the tree - var currentTranslate = d3.transform(container.attr('transform')).translate; - var dx = d3.event.wheelDeltaX / TRANSLATE_FACTOR + currentTranslate[0]; - moveTree(dx); - d3.event.stopPropagation(); - } - - moveTree = function(dx) { - dx = Math.min(0, Math.max(dx, -(view.width - DEFAULT_CARD_WIDTH))); - zoom.translate([dx, 0]); - container.attr('transform', 'translate(' + dx + ', 0)'); - nodes.forEach(function(d) { - d.xoffset = dx; - }); - view.stages.set(nodes); - }; - - container = svg.enter().append('svg') - .attr('class', 'links') - .attr('width', '100%') - .attr('height', '100%') - .call(zoom) - .on('wheel.zoom', pan) - .append('g'); - - // remove unneeded event handlers - svg.on('dblclick.zoom', null) - .on('touchstart.zoom', null) - .on('mousewheel.zoom', null) - .on('MozMousePixelScroll.zoom', null); - - // links are svg elements - var link = container.selectAll('path.link') - .data(links, function(d) { return d.target.id; }); - - link.enter().insert('path', 'g') - .attr('class', 'link') - .attr('d', elbow); - - link.exit().remove(); - }, - _computeExecTimes: function(node) { - if (!node.children || node.children.length === 0) { - // leaf nodes - node.prevStageExecTimeMS = 0; - } else { - var execTimes = _.map(node.children, this._computeExecTimes.bind(this)); - node.prevStageExecTimeMS = _.max(execTimes); - } - if (node.isShard) { - node.curStageExecTimeMS = node.prevStageExecTimeMS; - } - // never return negative values - node.curStageExecTimeMS = Math.max(0, node.curStageExecTimeMS); - return node.curStageExecTimeMS; - } -}); From 76865f8f71705774c457ce9db0235cb240cff864 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 14:31:11 -0400 Subject: [PATCH 04/15] Fix Intercom email can't be blank error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The below started appearing when intercom messenger was upgraded several weeks ago. The root cause is user resource would send `email:‘’` which fails validation on Intercom's API server. If we instead allow email to default to undefined, no empty string is sent to intercom’s server and validation passes. After this is merged, we’ll also need to change models/user.js in Compass. ``` Failed to load resource: the server responded with a status of 400 (Bad Request) Uncaught (in promise {"type":"error.list","request_id":"anscsvqg0eamtn7i98t0","errors":[{"cod e":"parameter_invalid","message":"Email can't be blank"}]} ``` --- package.json | 2 +- src/app/models/user.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index acc943a1ff0..c0b854858c0 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "mongodb-explain-plan-model": "^0.2.1", "mongodb-extended-json": "^1.7.0", "mongodb-instance-model": "^3.1.7", - "mongodb-js-metrics": "^1.5.0", + "mongodb-js-metrics": "^1.5.2", "mongodb-language-model": "^0.3.3", "mongodb-ns": "^1.0.3", "mongodb-schema": "^5.0.0", diff --git a/src/app/models/user.js b/src/app/models/user.js index 78ffdf85a8a..d93dfc4c412 100644 --- a/src/app/models/user.js +++ b/src/app/models/user.js @@ -22,7 +22,12 @@ var User = Model.extend(storageMixin, { } }, name: 'string', - email: 'string', + email: { + type: 'any', + default: undefined, + required: false, + allowNull: true + }, createdAt: 'date', lastUsed: 'date', avatarUrl: 'string', From 04694e9faffb36c8b36d69a2735ccf447a171015 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 14:38:55 -0400 Subject: [PATCH 05/15] :fire: Remove old schema modules Were ported to react some time ago. See ./src/internal-packages/schema --- src/app/minicharts/array-root.jade | 12 - src/app/minicharts/array-root.js | 23 - src/app/minicharts/d3-tip.js | 349 ------------- src/app/minicharts/d3fns/boolean.js | 88 ---- src/app/minicharts/d3fns/coordinates.js | 170 ------- src/app/minicharts/d3fns/date.js | 386 --------------- src/app/minicharts/d3fns/few.js | 281 ----------- src/app/minicharts/d3fns/geo.js | 474 ------------------ src/app/minicharts/d3fns/index.js | 9 - src/app/minicharts/d3fns/many.js | 386 --------------- src/app/minicharts/d3fns/mapstyle.js | 89 ---- src/app/minicharts/d3fns/number.js | 135 ----- src/app/minicharts/d3fns/shared.js | 46 -- src/app/minicharts/d3fns/string.js | 90 ---- src/app/minicharts/d3fns/tooltip.jade | 3 - src/app/minicharts/document-root.jade | 4 - src/app/minicharts/document-root.js | 15 - src/app/minicharts/index.js | 167 ------- src/app/minicharts/index.less | 336 ------------- src/app/minicharts/minichart.jade | 1 - src/app/minicharts/querybuilder.js | 634 ------------------------ src/app/minicharts/svg-template.jade | 6 - src/app/minicharts/unique.jade | 10 - src/app/minicharts/unique.js | 73 --- src/app/minicharts/viz.js | 155 ------ src/app/models/sampled-schema.js | 252 ---------- src/app/refine-view/index.jade | 6 - src/app/refine-view/index.js | 194 -------- src/app/refine-view/index.less | 27 - src/app/sampling-message/index.jade | 14 - src/app/sampling-message/index.js | 96 ---- src/app/sampling-message/index.less | 11 - src/app/statusbar/index.jade | 14 - src/app/statusbar/index.js | 188 ------- src/app/statusbar/schema-subview.jade | 31 -- src/app/statusbar/schema-subview.js | 179 ------- 36 files changed, 4954 deletions(-) delete mode 100644 src/app/minicharts/array-root.jade delete mode 100644 src/app/minicharts/array-root.js delete mode 100644 src/app/minicharts/d3-tip.js delete mode 100644 src/app/minicharts/d3fns/boolean.js delete mode 100644 src/app/minicharts/d3fns/coordinates.js delete mode 100644 src/app/minicharts/d3fns/date.js delete mode 100644 src/app/minicharts/d3fns/few.js delete mode 100644 src/app/minicharts/d3fns/geo.js delete mode 100644 src/app/minicharts/d3fns/index.js delete mode 100644 src/app/minicharts/d3fns/many.js delete mode 100644 src/app/minicharts/d3fns/mapstyle.js delete mode 100644 src/app/minicharts/d3fns/number.js delete mode 100644 src/app/minicharts/d3fns/shared.js delete mode 100644 src/app/minicharts/d3fns/string.js delete mode 100644 src/app/minicharts/d3fns/tooltip.jade delete mode 100644 src/app/minicharts/document-root.jade delete mode 100644 src/app/minicharts/document-root.js delete mode 100644 src/app/minicharts/index.js delete mode 100644 src/app/minicharts/index.less delete mode 100644 src/app/minicharts/minichart.jade delete mode 100644 src/app/minicharts/querybuilder.js delete mode 100644 src/app/minicharts/svg-template.jade delete mode 100644 src/app/minicharts/unique.jade delete mode 100644 src/app/minicharts/unique.js delete mode 100644 src/app/minicharts/viz.js delete mode 100644 src/app/models/sampled-schema.js delete mode 100644 src/app/refine-view/index.jade delete mode 100644 src/app/refine-view/index.js delete mode 100644 src/app/refine-view/index.less delete mode 100644 src/app/sampling-message/index.jade delete mode 100644 src/app/sampling-message/index.js delete mode 100644 src/app/sampling-message/index.less delete mode 100644 src/app/statusbar/index.jade delete mode 100644 src/app/statusbar/index.js delete mode 100644 src/app/statusbar/schema-subview.jade delete mode 100644 src/app/statusbar/schema-subview.js diff --git a/src/app/minicharts/array-root.jade b/src/app/minicharts/array-root.jade deleted file mode 100644 index 5fc53a1b19f..00000000000 --- a/src/app/minicharts/array-root.jade +++ /dev/null @@ -1,12 +0,0 @@ -div(data-hook='viz-container') - dl - if fieldNames - dt Array of documents with #{fieldsPluralized} - dd - - dt Array lengths - dd - ul.list-inline - li min: #{min_length}, - li average: #{average_length}, - li max: #{max_length} diff --git a/src/app/minicharts/array-root.js b/src/app/minicharts/array-root.js deleted file mode 100644 index 4bd300d138c..00000000000 --- a/src/app/minicharts/array-root.js +++ /dev/null @@ -1,23 +0,0 @@ -var VizView = require('./viz'); -var _ = require('lodash'); -var pluralize = require('pluralize'); -var numeral = require('numeral'); - -var arrayRootTemplate = require('./array-root.jade'); -// var debug = require('debug')('mongodb-compass:minicharts:array-root'); - -module.exports = VizView.extend({ - template: arrayRootTemplate, - render: function() { - var parsed = { - average_length: numeral(this.model.average_length).format('0.0[0]'), - min_length: _.min(this.model.lengths), - max_length: _.max(this.model.lengths) - }; - if (this.model.parent.arrayFields) { - parsed.fieldNames = this.model.parent.arrayFields.pluck('name'); - parsed.fieldsPluralized = pluralize('nested field', parsed.fieldNames.length, true); - } - this.renderWithTemplate(parsed); - } -}); diff --git a/src/app/minicharts/d3-tip.js b/src/app/minicharts/d3-tip.js deleted file mode 100644 index d84c2514f9d..00000000000 --- a/src/app/minicharts/d3-tip.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint no-use-before-define: 0, one-var: 0, no-else-return: 0, no-unused-vars: 0, eqeqeq: 0, no-shadow: 0, yoda: 0, consistent-return: 0, one-var: 0, camelcase: 0 */ -// d3.tip -// Copyright (c) 2013 Justin Palmer -// -// Tooltips for d3.js SVG visualizations - -(function(root, factory) { - if (typeof module === 'object' && module.exports) { - // CommonJS - module.exports = function(d3) { - d3.tip = factory(d3); - return d3.tip; - }; - } else { - // Browser global. - root.d3.tip = factory(root.d3); - } -}(this, function(d3) { - // Public - contructs a new tooltip - // - // Returns a tip - return function() { - var direction = d3_tip_direction; - var offset = d3_tip_offset; - var html = d3_tip_html; - var node = initNode(); - var svg = null; - var point = null; - var target = null; - - function tip(vis) { - svg = getSVGNode(vis); - if (!svg) { - return; - } - point = svg.createSVGPoint(); - document.body.appendChild(node); - } - - // Public - show the tooltip on the screen - // - // Returns a tip - tip.show = function() { - var args = Array.prototype.slice.call(arguments); - if (args[args.length - 1] instanceof SVGElement) { - target = args.pop(); - } - - var content = html.apply(this, args), - poffset = offset.apply(this, args), - dir = direction.apply(this, args), - nodel = getNodeEl(), - i = directions.length, - coords, - scrollTop = document.documentElement.scrollTop || document.body.scrollTop, - scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; - - nodel.html(content) - .style({ - opacity: 1, - 'pointer-events': 'all' - }); - - while (i--) { - nodel.classed(directions[i], false); - } - coords = direction_callbacks.get(dir).apply(this); - nodel.classed(dir, true).style({ - top: (coords.top + poffset[0] + scrollTop).toString() + 'px', - left: (coords.left + poffset[1] + scrollLeft).toString() + 'px' - }); - - return tip; - }; - - // Public - hide the tooltip - // - // Returns a tip - tip.hide = function() { - var nodel = getNodeEl(); - nodel.style({ - opacity: 0, - 'pointer-events': 'none' - }); - return tip; - }; - - // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. - // - // n - name of the attribute - // v - value of the attribute - // - // Returns tip or attribute value - tip.attr = function(n, v) { - if (arguments.length < 2 && typeof n === 'string') { - return getNodeEl().attr(n); - } else { - var args = Array.prototype.slice.call(arguments); - d3.selection.prototype.attr.apply(getNodeEl(), args); - } - - return tip; - }; - - // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. - // - // n - name of the property - // v - value of the property - // - // Returns tip or style property value - tip.style = function(n, v) { - if (arguments.length < 2 && typeof n === 'string') { - return getNodeEl().style(n); - } else { - var args = Array.prototype.slice.call(arguments); - d3.selection.prototype.style.apply(getNodeEl(), args); - } - - return tip; - }; - - // Public: Set or get the direction of the tooltip - // - // v - One of n(north), s(south), e(east), or w(west), nw(northwest), - // sw(southwest), ne(northeast) or se(southeast) - // - // Returns tip or direction - tip.direction = function(v) { - if (!arguments.length) { - return direction; - } - direction = v == null ? v : d3.functor(v); - - return tip; - }; - - // Public: Sets or gets the offset of the tip - // - // v - Array of [x, y] offset - // - // Returns offset or - tip.offset = function(v) { - if (!arguments.length) { - return offset; - } - offset = v == null ? v : d3.functor(v); - - return tip; - }; - - // Public: sets or gets the html value of the tooltip - // - // v - String value of the tip - // - // Returns html value or tip - tip.html = function(v) { - if (!arguments.length) { - return html; - } - html = v == null ? v : d3.functor(v); - - return tip; - }; - - // Public: destroys the tooltip and removes it from the DOM - // - // Returns a tip - tip.destroy = function() { - if (node) { - getNodeEl().remove(); - node = null; - } - return tip; - }; - - function d3_tip_direction() { - return 'n'; - } - function d3_tip_offset() { - return [0, 0]; - } - function d3_tip_html() { - return ' '; - } - - var direction_callbacks = d3.map({ - n: direction_n, - s: direction_s, - e: direction_e, - w: direction_w, - nw: direction_nw, - ne: direction_ne, - sw: direction_sw, - se: direction_se - }), - - directions = direction_callbacks.keys(); - - function direction_n() { - var bbox = getScreenBBox(); - return { - top: bbox.n.y - node.offsetHeight, - left: bbox.n.x - node.offsetWidth / 2 - }; - } - - function direction_s() { - var bbox = getScreenBBox(); - return { - top: bbox.s.y, - left: bbox.s.x - node.offsetWidth / 2 - }; - } - - function direction_e() { - var bbox = getScreenBBox(); - return { - top: bbox.e.y - node.offsetHeight / 2, - left: bbox.e.x - }; - } - - function direction_w() { - var bbox = getScreenBBox(); - return { - top: bbox.w.y - node.offsetHeight / 2, - left: bbox.w.x - node.offsetWidth - }; - } - - function direction_nw() { - var bbox = getScreenBBox(); - return { - top: bbox.nw.y - node.offsetHeight, - left: bbox.nw.x - node.offsetWidth - }; - } - - function direction_ne() { - var bbox = getScreenBBox(); - return { - top: bbox.ne.y - node.offsetHeight, - left: bbox.ne.x - }; - } - - function direction_sw() { - var bbox = getScreenBBox(); - return { - top: bbox.sw.y, - left: bbox.sw.x - node.offsetWidth - }; - } - - function direction_se() { - var bbox = getScreenBBox(); - return { - top: bbox.se.y, - left: bbox.e.x - }; - } - - function initNode() { - var node = d3.select(document.createElement('div')); - node.style({ - position: 'absolute', - top: 0, - opacity: 0, - 'pointer-events': 'none', - 'box-sizing': 'border-box' - }); - - return node.node(); - } - - function getSVGNode(el) { - el = el.node(); - if (!el) { - return; - } - if (el.tagName.toLowerCase() === 'svg') { - return el; - } - - return el.ownerSVGElement; - } - - function getNodeEl() { - if (node === null) { - node = initNode(); - // re-add node to DOM - document.body.appendChild(node); - } - return d3.select(node); - } - - // Private - gets the screen coordinates of a shape - // - // Given a shape on the screen, will return an SVGPoint for the directions - // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), - // sw(southwest). - // - // +-+-+ - // | | - // + + - // | | - // +-+-+ - // - // Returns an Object {n, s, e, w, nw, sw, ne, se} - function getScreenBBox() { - var targetel = target || d3.event.target; - - while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) { - targetel = targetel.parentNode; - } - - var bbox = {}, - matrix = targetel.getScreenCTM(), - tbbox = targetel.getBBox(), - width = tbbox.width, - height = tbbox.height, - x = tbbox.x, - y = tbbox.y; - - point.x = x; - point.y = y; - bbox.nw = point.matrixTransform(matrix); - point.x += width; - bbox.ne = point.matrixTransform(matrix); - point.y += height; - bbox.se = point.matrixTransform(matrix); - point.x -= width; - bbox.sw = point.matrixTransform(matrix); - point.y -= height / 2; - bbox.w = point.matrixTransform(matrix); - point.x += width; - bbox.e = point.matrixTransform(matrix); - point.x -= width / 2; - point.y -= height / 2; - bbox.n = point.matrixTransform(matrix); - point.y += height; - bbox.s = point.matrixTransform(matrix); - - return bbox; - } - - return tip; - }; -})); diff --git a/src/app/minicharts/d3fns/boolean.js b/src/app/minicharts/d3fns/boolean.js deleted file mode 100644 index 3f7cc89656a..00000000000 --- a/src/app/minicharts/d3fns/boolean.js +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var few = require('./few'); -var shared = require('./shared'); -// var debug = require('debug')('mongodb-compass:minicharts:boolean'); - - -var minicharts_d3fns_boolean = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - var options = { - view: null - }; - var fewChart = few(); - var margin = shared.margin; - // --- end chart setup --- - - function chart(selection) { - selection.each(function(data) { - var el = d3.select(this); - var innerWidth = width - margin.left - margin.right; - var innerHeight = height - margin.top - margin.bottom; - - // group by true/false - var grouped = _(data) - .groupBy(function(d) { - return d; - }) - .defaults({ - false: [], - true: [] - }) - .map(function(v, k) { - return { - label: k, - value: k === 'true', - count: v.length - }; - }) - .sortByOrder('label', [false]) // order: false, true - .value(); - - fewChart - .width(innerWidth) - .height(innerHeight) - .options(options); - - var g = el.selectAll('g').data([grouped]); - - // append g element if it doesn't exist yet - g.enter() - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - g.call(fewChart); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_boolean; diff --git a/src/app/minicharts/d3fns/coordinates.js b/src/app/minicharts/d3fns/coordinates.js deleted file mode 100644 index dfe20cefa65..00000000000 --- a/src/app/minicharts/d3fns/coordinates.js +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var shared = require('./shared'); - -var tooltipTemplate = require('./tooltip.jade'); - -require('../d3-tip')(d3); - -// var debug = require('debug')('mongodb-compass:minicharts:coordinates'); - -var minicharts_d3fns_coordinates = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - var options = { - view: null - }; - var margin = _.clone(shared.margin); - margin.bottom = 20; - - var xScale = d3.scale.linear(); - var yScale = d3.scale.linear(); - - var xAxis = d3.svg.axis() - .ticks(10) - .scale(xScale) - .orient('bottom'); - - var yAxis = d3.svg.axis() - .ticks(6) - .scale(yScale) - .orient('left'); - - var coordFormat = d3.format('.1f'); - - // set up tooltips - var tip = d3.tip() - .attr('class', 'd3-tip') - .direction('n') - .offset([-9, 0]); - - // --- end chart setup --- - - function chart(selection) { - selection.each(function(data) { - var el = d3.select(this); - var innerWidth = width - margin.left - margin.right; - var innerHeight = height - margin.top - margin.bottom; - - // setup tool tips - tip.html(function(d) { - return tooltipTemplate({ - label: 'lng: ' + coordFormat(d[0]) + ', lat: ' + coordFormat(d[1]) - }); - }); - el.call(tip); - - xScale - .domain([ - d3.min(data, function(d) { - return d[0]; - }) - 3, - d3.max(data, function(d) { - return d[0]; - }) + 3 - ]) - .range([0, innerWidth]); - - yScale - .domain([ - d3.min(data, function(d) { - return d[1]; - }) - 3, - d3.max(data, function(d) { - return d[1]; - }) + 3 - ]) - .range([innerHeight, 0]); - - var g = el.selectAll('g').data([null]); - - // append g element if it doesn't exist yet - g.enter() - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - .attr('width', innerWidth) - .attr('height', innerHeight); - - var x = g.selectAll('.x.axis').data([null]); - x.enter().append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0, ' + innerHeight + ')') - .append('text') - // .attr('class', 'label') - .attr('x', innerWidth) - .attr('y', -6) - .style('text-anchor', 'end') - .text('lng'); - x.call(xAxis); - - var y = g.selectAll('.y.axis').data([null]); - y.enter().append('g') - .attr('class', 'y axis') - .append('text') - // .attr('class', 'label') - .attr('transform', 'rotate(-90)') - .attr('y', 6) - .attr('dy', '.71em') - .style('text-anchor', 'end') - .text('lat'); - y.call(yAxis); - - // select all g.bar elements - var circle = g.selectAll('circle.circle') - .data(data); - - circle - .transition() // only apply transition to already existing elements - .attr('cx', function(d) { - return xScale(d[0]); - }) - .attr('cy', function(d) { - return yScale(d[1]); - }); - - circle.enter().append('circle') - .attr('class', 'circle') - .attr('cx', function(d) { - return xScale(d[0]); - }) - .attr('cy', function(d) { - return yScale(d[1]); - }) - .attr('r', 4.5) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); - - circle.exit().remove(); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_coordinates; diff --git a/src/app/minicharts/d3fns/date.js b/src/app/minicharts/d3fns/date.js deleted file mode 100644 index 0af3894af73..00000000000 --- a/src/app/minicharts/d3fns/date.js +++ /dev/null @@ -1,386 +0,0 @@ -/* eslint no-use-before-define: 0, camelcase:0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var $ = require('jquery'); -var moment = require('moment'); -var shared = require('./shared'); -var many = require('./many'); -var raf = require('raf'); - -// var debug = require('debug')('mongodb-compass:minicharts:date'); - -require('../d3-tip')(d3); - -function generateDefaults(n) { - var doc = {}; - _.each(_.range(n), function(d) { - doc[d] = []; - }); - return doc; -} - -function extractTimestamp(d) { - return d._bsontype === 'ObjectID' ? d.getTimestamp() : d; -} - -var minicharts_d3fns_date = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - var upperRatio = 2.5; - var upperMargin = 20; - - var options = { - view: null - }; - var weekdayLabels = moment.weekdays(); - - // A formatter for dates - var format = d3.time.format('%Y-%m-%d %H:%M:%S'); - - var margin = shared.margin; - var barcodeX = d3.time.scale(); - - // set up tooltips - var tip = d3.tip() - .attr('class', 'd3-tip') - .html(function(d) { - return d.label; - }) - .direction('n') - .offset([-9, 0]); - - var brush = d3.svg.brush() - .x(barcodeX) - .on('brushstart', brushstart) - .on('brush', brushed) - .on('brushend', brushend); - - function brushstart(clickedLine) { - // remove selections and half selections - var lines = d3.selectAll(options.view.queryAll('.selectable')); - lines.classed('selected', function() { - return this === clickedLine; - }); - lines.classed('unselected', function() { - return this !== clickedLine; - }); - } - - function brushed() { - var lines = d3.selectAll(options.view.queryAll('.selectable')); - var numSelected = options.view.queryAll('.selectable.selected').length; - var s = brush.extent(); - - lines.classed('selected', function(d) { - return s[0] <= d.ts && d.ts <= s[1]; - }); - lines.classed('unselected', function(d) { - var pos = barcodeX(d.dt); - return s[0] > pos || pos > s[1]; - }); - if (!options.view) { - return; - } - if (numSelected !== options.view.queryAll('.selectable.selected').length) { - // number of selected items has changed, trigger querybuilder event - var evt = { - type: 'drag', - source: 'date' - }; - options.view.trigger('querybuilder', evt); - } - } - - function brushend() { - var lines = d3.selectAll(options.view.queryAll('.selectable')); - if (brush.empty()) { - lines.classed('selected', false); - lines.classed('unselected', false); - } - d3.select(this).call(brush.clear()); - - if (!options.view) { - return; - } - var evt = { - type: 'drag', - source: 'many' - }; - options.view.trigger('querybuilder', evt); - } - var handleClick = function(d) { - var evt = { - d: d, - self: this, - evt: d3.event, - type: 'click', - source: 'date' - }; - options.view.trigger('querybuilder', evt); - }; - - function handleMouseDown() { - var line = this; - var parent = $(this).closest('.minichart'); - var background = parent.find('g.brush > rect.background')[0]; - var brushNode = parent.find('g.brush')[0]; - var start = barcodeX.invert(d3.mouse(background)[0]); - var brushstartOnce = _.once(function() { - brushstart.call(brushNode, line); - }); - - var w = d3.select(window) - .on('mousemove', mousemove) - .on('mouseup', mouseup); - - d3.event.preventDefault(); // disable text dragging - - function mousemove() { - brushstartOnce(); - var extent = [start, barcodeX.invert(d3.mouse(background)[0])]; - d3.select(brushNode).call(brush.extent(_.sortBy(extent))); - brushed.call(brushNode); - } - - function mouseup() { - // bar.classed('selected', true); - w.on('mousemove', null).on('mouseup', null); - if (brush.empty()) { - // interpret as click - handleClick.call(line, d3.select(line).data()[0]); - } else { - brushend.call(brushNode); - } - } - } - - - function chart(selection) { - selection.each(function(data) { - var values = data.map(function(d) { - var ts = extractTimestamp(d); - return { - label: format(ts), - ts: ts, - value: d, - count: 1, - dx: 0 // this will trigger `$lte` instead of `$lt` for ranges in the query builder - }; - }); - - // without `-1` the tooltip won't always trigger on the rightmost value - var innerWidth = width - margin.left - margin.right - 1; - var innerHeight = height - margin.top - margin.bottom; - var el = d3.select(this); - - var barcodeTop = Math.floor(innerHeight / 2 + 15); - var barcodeBottom = Math.floor(innerHeight - 10); - - var upperBarBottom = innerHeight / 2 - 20; - - barcodeX - .domain(d3.extent(values, function(d) { - return d.ts; - })) - .range([0, innerWidth]); - - // group by weekdays - var weekdays = _(values) - .groupBy(function(d) { - return moment(d.ts).weekday(); - }) - .defaults(generateDefaults(7)) - .map(function(d, i) { - return { - label: weekdayLabels[i], - count: d.length - }; - }) - .value(); - - // group by hours - var hourLabels = d3.range(24); - var hours = _(values) - .groupBy(function(d) { - return d.ts.getHours(); - }) - .defaults(generateDefaults(24)) - .map(function(d, i) { - return { - label: hourLabels[i] + ':00', - count: d.length - }; - }) - .value(); - el.call(tip); - - var g = el.selectAll('g').data([data]); - - // append g element if it doesn't exist yet - var gEnter = g.enter() - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - gEnter.append('g') - .attr('class', 'weekday') - .append('text') - .attr('class', 'date-icon fa-fw') - .attr('x', 0) - .attr('dx', '-0.6em') - .attr('y', 0) - .attr('dy', '1em') - .attr('text-anchor', 'end') - .attr('font-family', 'FontAwesome') - .text('\uf133'); - - gEnter.append('g') - .attr('class', 'hour') - .attr('transform', 'translate(' + (innerWidth / (upperRatio + 1) + upperMargin) + ', 0)') - .append('text') - .attr('class', 'date-icon fa-fw') - .attr('x', 0) - .attr('dx', '-0.6em') - .attr('y', 0) - .attr('dy', '1em') - .attr('text-anchor', 'end') - .attr('font-family', 'FontAwesome') - .text('\uf017'); - - var gBrush = g.selectAll('.brush').data([0]); - gBrush.enter().append('g') - .attr('class', 'brush') - .call(brush) - .selectAll('rect') - .attr('y', barcodeTop) - .attr('height', barcodeBottom - barcodeTop); - - var lineContainer = gEnter.append('g') - .attr('class', 'line-container'); - - var lines = lineContainer.selectAll('.selectable').data(values, function(d) { - return d.ts; - }); - - lines.enter().append('line') - .attr('class', 'line selectable') - .style('opacity', function() { - return lines.size() > 200 ? 0.3 : 1.0; - }) - .on('mouseover', tip.show) - .on('mouseout', tip.hide) - .on('mousedown', handleMouseDown); - - // disabling direct onClick handler in favor of click-drag - // .on('click', handleClick); - - lines - .attr('y1', barcodeTop) - .attr('y2', barcodeBottom) - .attr('x2', function(d) { - return barcodeX(d.ts); - }) - .attr('x1', function(d) { - return barcodeX(d.ts); - }); - - lines.exit().remove(); - - var text = g.selectAll('.text') - .data(barcodeX.domain()); - - text.enter().append('text') - .attr('class', 'text') - .attr('dy', '0.75em') - .attr('y', barcodeBottom + 5); - - text - .attr('x', function(d, i) { - return i * innerWidth; - }) - .attr('text-anchor', function(d, i) { - return i ? 'end' : 'start'; - }) - .text(function(d, i) { - if (format(barcodeX.domain()[0]) === format(barcodeX.domain()[1])) { - if (i === 0) { - return 'inserted: ' + format(d); - } - } else { - return (i ? 'last: ' : 'first: ') + format(d); - } - }); - - text.exit().remove(); - - var weekdayContainer = g.select('g.weekday').data([weekdays]); - - raf(function() { - var chartWidth = innerWidth / (upperRatio + 1) - upperMargin; - var manyChart = many() - .width(chartWidth) - .height(upperBarBottom) - .options({ - selectable: false, - bgbars: true, - labels: { - 'text-anchor': 'middle', - text: function(d) { - return d.label[0]; - } - }, - view: options.view - }); - weekdayContainer.call(manyChart); - }); - - var hourContainer = g.select('g.hour').data([hours]); - raf(function() { - var chartWidth = innerWidth / (upperRatio + 1) * upperRatio - upperMargin; - var manyChart = many() - .width(chartWidth) - .height(upperBarBottom) - .options({ - selectable: false, - bgbars: true, - labels: { - text: function(d, i) { - return i % 6 === 0 || i === 23 ? d.label : ''; - } - }, - view: options.view - }); - hourContainer.call(manyChart); - }); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - - -module.exports = minicharts_d3fns_date; diff --git a/src/app/minicharts/d3fns/few.js b/src/app/minicharts/d3fns/few.js deleted file mode 100644 index ca4b7202608..00000000000 --- a/src/app/minicharts/d3fns/few.js +++ /dev/null @@ -1,281 +0,0 @@ -/* eslint no-use-before-define: 0, camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var $ = require('jquery'); -var shared = require('./shared'); - -var tooltipTemplate = require('./tooltip.jade'); -// var debug = require('debug')('mongodb-compass:minicharts:few'); - -require('../d3-tip')(d3); - -var minicharts_d3fns_few = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - var barHeight = 25; - var brushHeight = 80; - var options = { - view: null - }; - - var xScale = d3.scale.linear(); - - var brush = d3.svg.brush() - .x(xScale) - .on('brush', brushed) - .on('brushend', brushend); - - // set up tooltips - var tip = d3.tip() - .attr('class', 'd3-tip') - .direction('n') - .offset([-9, 0]); - // --- end chart setup --- - - function handleClick(d) { - if (!options.view) { - return; - } - var fgRect = $(this).siblings('rect.selectable')[0]; - var evt = { - d: d, - self: fgRect, - evt: d3.event, - type: 'click', - source: 'few' - }; - options.view.trigger('querybuilder', evt); - } - - function brushstart(clickedBar) { - // remove selections and half selections - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - bars.classed('half', false); - bars.classed('selected', function() { - return this === clickedBar; - }); - bars.classed('unselected', function() { - return this !== clickedBar; - }); - } - - function brushed() { - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - var numSelected = options.view.queryAll('rect.selectable.selected').length; - var s = brush.extent(); - - bars.classed('selected', function(d) { - var left = d.xpos; - var right = left + d.count; - return s[0] <= right && left <= s[1]; - }); - bars.classed('unselected', function(d) { - var left = d.xpos; - var right = left + d.count; - return s[0] > right || left > s[1]; - }); - - if (!options.view) { - return; - } - if (numSelected !== options.view.queryAll('rect.selectable.selected').length) { - // number of selected items has changed, trigger querybuilder event - var evt = { - type: 'drag', - source: 'many' - }; - options.view.trigger('querybuilder', evt); - } - } - - function brushend() { - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - if (brush.empty()) { - bars.classed('selected', false); - bars.classed('unselected', false); - } - d3.select(this).call(brush.clear()); - - if (!options.view) { - return; - } - var evt = { - type: 'drag', - source: 'many' - }; - options.view.trigger('querybuilder', evt); - } - - function handleMouseDown() { - var bar = this; - var rect = $(this).find('rect.selectable')[0]; - var parent = $(this).closest('.minichart'); - var background = parent.find('g.brush > rect.background')[0]; - var brushNode = parent.find('g.brush')[0]; - var start = xScale.invert(d3.mouse(background)[0]); - var brushstartOnce = _.once(function() { - brushstart.call(brushNode, rect); - }); - - var w = d3.select(window) - .on('mousemove', mousemove) - .on('mouseup', mouseup); - - d3.event.preventDefault(); // disable text dragging - - function mousemove() { - brushstartOnce(); - var extent = [start, xScale.invert(d3.mouse(background)[0])]; - d3.select(brushNode).call(brush.extent(_.sortBy(extent))); - brushed.call(brushNode); - } - - function mouseup() { - // bar.classed('selected', true); - w.on('mousemove', null).on('mouseup', null); - if (brush.empty()) { - // interpret as click - handleClick.call(bar, d3.select(bar).data()[0]); - } else { - brushend.call(brushNode); - } - } - } - - function chart(selection) { - selection.each(function(data) { - var values = _.map(data, 'count'); - _.each(data, function(d, i) { - data[i].xpos = _.sum(_(data) - .slice(0, i) - .map('count') - .value() - ); - }); - var sumValues = d3.sum(values); - var maxValue = d3.max(values); - var percentFormat = shared.friendlyPercentFormat(maxValue / sumValues * 100); - var el = d3.select(this); - - xScale - .domain([0, sumValues]) - .range([0, width]); - - // setup tool tips - tip.html(function(d, i) { - if (typeof d.tooltip === 'function') { - return d.tooltip(d, i); - } - return d.tooltip || tooltipTemplate({ - label: shared.truncateTooltip(d.label), - count: percentFormat(d.count / sumValues * 100, false) - }); - }); - el.call(tip); - - var gBrush = el.selectAll('.brush').data([0]); - gBrush.enter().append('g') - .attr('class', 'brush') - .call(brush) - .selectAll('rect') - .attr('y', (height - brushHeight) / 2) - .attr('height', brushHeight); - - // select all g.bar elements - var bar = el.selectAll('g.bar') - .data(data, function(d) { - return d.label; // identify data by its label - }); - - bar - .transition() // only apply transition to already existing bars - .attr('transform', function(d) { - return 'translate(' + xScale(d.xpos) + ', ' + (height - barHeight) / 2 + ')'; - }); - - var barEnter = bar.enter().append('g') - .attr('class', 'bar few') - .attr('transform', function(d, i) { // repeat transform attr here but without transition - var xpos = _.sum(_(data) - .slice(0, i) - .pluck('count') - .value() - ); - d.xpos = xpos; - return 'translate(' + xScale(xpos) + ', ' + (height - barHeight) / 2 + ')'; - }) - .on('mousedown', handleMouseDown); - - barEnter.append('rect') - .attr('class', function(d, i) { - return 'selectable fg fg-' + i; - }) - .attr('y', 0) - .attr('x', 0) - .attr('height', barHeight); - - barEnter.append('text') - .attr('y', barHeight / 2) - .attr('dy', '0.3em') - .attr('dx', 10) - .attr('text-anchor', 'start') - .attr('fill', 'white'); - - barEnter.append('rect') - .attr('class', 'glass') - .attr('y', 0) - .attr('x', 0) - .attr('height', barHeight) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); - - bar.select('rect.selectable') - .transition() - .attr('width', function(d) { - return xScale(d.count); - }); - - bar.select('rect.glass') - .transition() - .attr('width', function(d) { - return xScale(d.count); - }); - - bar.select('text') - .text(function(d) { - return d.label; - }); - - bar.exit().remove(); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_few; diff --git a/src/app/minicharts/d3fns/geo.js b/src/app/minicharts/d3fns/geo.js deleted file mode 100644 index ed3ebbb7b53..00000000000 --- a/src/app/minicharts/d3fns/geo.js +++ /dev/null @@ -1,474 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var shared = require('./shared'); -// var debug = require('debug')('mongodb-compass:minicharts:geo'); -var app = require('ampersand-app'); -var turfDistance = require('turf-distance'); -var turfPoint = require('turf-point'); -var turfDestination = require('turf-destination'); - -// var metrics = require('mongodb-js-metrics')(); - -var SELECTED_COLOR = '#F68A1E'; -var UNSELECTED_COLOR = '#43B1E5'; -var CONTROL_COLOR = '#ed271c'; -var TOKEN = 'pk.eyJ1IjoibW9uZ29kYi1jb21wYXNzIiwiYSI6ImNpbWUxZjNudjAwZTZ0emtrczByanZ4MzIifQ.6Mha4zoflraopcZKOLSpYQ'; -var MAPBOX_API_URL = 'https://compass-maps.mongodb.com/api.mapbox.com'; -var MAPBOX_CLIENT_URL = MAPBOX_API_URL + '/mapbox-gl-js/v0.15.0/mapbox-gl.js'; - -var minicharts_d3fns_geo = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - - var map = null; - var circleControl; - var mapboxgl; - - var options = { - view: null - }; - - var circleCenter; - var circleOuter; // control points - var circleSelected = false; // have we completed the circle? - var margin = shared.margin; - - function CircleSelector(svg) { - var dragging = false; // track whether we are dragging - var container = svg; // the container we render our points in - - // we expose events on our component - var dispatch = d3.dispatch('update', 'clear'); - - // this will likely be overriden by leaflet projection - var project = d3.geo.mercator(); - var unproject = d3.geo.mercator().invert; - - var update; - - function querybuilder() { - var evt = { - type: 'geo', - source: 'geo' - }; - if (circleCenter && circleOuter) { - var mileDistance = turfDistance( - turfPoint([circleCenter.lng, circleCenter.lat]), - turfPoint([circleOuter.lng, circleOuter.lat]), - 'miles' - ); - evt.center = [circleCenter.lng, circleCenter.lat]; - evt.distance = mileDistance; - } - options.view.trigger('querybuilder', evt); - } - - function distance(ll0, ll1) { - var p0 = project(ll0); - var p1 = project(ll1); - var dist = Math.sqrt((p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y)); - return dist; - } - - var drag = d3.behavior.drag() - .on('drag', function(d, i) { - if (circleSelected) { - dragging = true; - var p = d3.mouse(svg.node()); - var ll = unproject([p[0], p[1]]); - if (i) { - circleOuter = ll; - } else { - var dlat = circleCenter.lat - ll.lat; - var dlng = circleCenter.lng - ll.lng; - circleCenter = ll; - circleOuter.lat -= dlat; - circleOuter.lng -= dlng; - } - update(); - querybuilder(); - } else { - return; - } - }) - .on('dragend', function() { - // kind of a dirty hack... - setTimeout(function() { - dragging = false; - querybuilder(); - }, 100); - }); - - function clear() { - circleCenter = null; - circleOuter = null; - circleSelected = false; - container.selectAll('circle.lasso').remove(); - container.selectAll('circle.control').remove(); - container.selectAll('line.lasso').remove(); - dispatch.clear(); - querybuilder(); - return; - } - - this.clear = clear; - - update = function(g) { - if (g) { - container = g; - } - if (!circleCenter || !circleOuter) return; - var dist = distance(circleCenter, circleOuter); - var circleLasso = container.selectAll('circle.lasso').data([dist]); - circleLasso.enter().append('circle') - .classed('lasso', true) - .style({ - stroke: SELECTED_COLOR, - 'stroke-width': 2, - fill: SELECTED_COLOR, - 'fill-opacity': 0.1 - }); - - circleLasso - .attr({ - cx: project(circleCenter).x, - cy: project(circleCenter).y, - r: dist - }); - - var line = container.selectAll('line.lasso').data([circleOuter]); - line.enter().append('line') - .classed('lasso', true) - .style({ - stroke: CONTROL_COLOR, - 'stroke-dasharray': '2 2' - }); - - line.attr({ - x1: project(circleCenter).x, - y1: project(circleCenter).y, - x2: project(circleOuter).x, - y2: project(circleOuter).y - }); - - var controls = container.selectAll('circle.control') - .data([circleCenter, circleOuter]); - controls.enter().append('circle') - .classed('control', true) - .style({ - 'cursor': 'move' - }); - - controls.attr({ - cx: function(d) { return project(d).x; }, - cy: function(d) { return project(d).y; }, - r: 5, - stroke: CONTROL_COLOR, - fill: CONTROL_COLOR, - 'fill-opacity': 0.7 - }) - .call(drag) - .on('mousedown', function() { - map.dragPan.disable(); - }) - .on('mouseup', function() { - map.dragPan.enable(); - }); - - dispatch.update(); - }; // end update() - this.update = update; - - function setCircle(centerLL, radiusMiles) { - var pCenter = turfPoint([centerLL[0], centerLL[1]]); - var pOuter = turfDestination(pCenter, radiusMiles, 45, 'miles'); - circleCenter = mapboxgl.LngLat.convert(pCenter.geometry.coordinates); - circleOuter = mapboxgl.LngLat.convert(pOuter.geometry.coordinates); - circleSelected = true; - update(); - } - this.setCircle = setCircle; - - svg.on('mousedown.circle', function() { - if (!d3.event.shiftKey) return; - if (dragging && circleSelected) return; - if (!dragging && circleSelected) { - // reset and remove circle - clear(); - return; - } - - map.dragPan.disable(); - var p = d3.mouse(this); - var ll = unproject([p[0], p[1]]); - - if (!circleCenter) { - // We set the center to the initial click - circleCenter = ll; - circleOuter = ll; - } - update(); - }); - - svg.on('mousemove.circle', function() { - if (circleSelected) return; - // we draw a guideline for where the next point would go. - var p = d3.mouse(this); - var ll = unproject([p[0], p[1]]); - circleOuter = ll; - update(); - querybuilder(); - }); - - svg.on('mouseup.circle', function() { - if (dragging && circleSelected) return; - - map.dragPan.enable(); - - var p = d3.mouse(this); - var ll = unproject([p[0], p[1]]); - - if (circleCenter) { - if (!circleSelected) { - circleOuter = ll; - circleSelected = true; - querybuilder(); - } - } - }); - - this.projection = function(val) { - if (!val) return project; - project = val; - return this; - }; - - this.inverseProjection = function(val) { - if (!val) return unproject; - unproject = val; - return this; - }; - - this.distance = function(ll) { - if (!ll) ll = circleOuter; - return distance(circleCenter, ll); - }; - - d3.rebind(this, dispatch, 'on'); - return this; - } - - function disableMapsFeature() { - // disable in preferences and persist - app.preferences.save('enableMaps', false); - delete window.google; - options.view.parent.render(); - } - - /** - * Load and configure the Mapbox client. - * - * @param {Function} done - Callback. - */ - function loadMapBoxScript(done) { - var script = document.createElement('script'); - script.setAttribute('type', 'text/javascript'); - script.src = MAPBOX_CLIENT_URL; - script.onerror = function() { - done('Error ocurred while loading Mapbox.'); - }; - script.onload = function() { - // Override mapbox to use our proxy server for API requests. - window.mapboxgl.config.API_URL = MAPBOX_API_URL; - done(null, window.mapboxgl); - }; - document.getElementsByTagName('head')[0].appendChild(script); - } - - // --- end chart setup --- - - function chart(selection) { - // load mapbox script - if (!window.mapboxgl) { - loadMapBoxScript(function(err) { - if (err) { - disableMapsFeature(); - } else { - chart.call(this, selection); - } - }); - return; - } - mapboxgl = window.mapboxgl; - - selection.each(function(data) { - function getLL(d) { - if (d instanceof mapboxgl.LngLat) return d; - return new mapboxgl.LngLat(+d[0], +d[1]); - } - function project(d) { - return map.project(getLL(d)); - } - - var el = d3.select(this); - - var svg; - var dots; - - var innerWidth = width - margin.left - margin.right; - var innerHeight = height - margin.top - margin.bottom; - - // append inner div once - var innerDiv = el.selectAll('div.map').data([null]); - innerDiv.enter().append('div') - .attr('class', 'map') - .style({ - width: innerWidth + 'px', - height: innerHeight + 'px', - padding: margin.top + 'px ' + margin.right + 'px ' + margin.bottom - + 'px ' + margin.left + 'px;' - }); - - // append info sprinkle - el.selectAll('i.help').data([null]).enter().append('i') - .classed('help', true) - .attr('data-hook', 'schema-geo-query-builder'); - - // compute bounds from data - var bounds = new mapboxgl.LngLatBounds(); - _.each(data, function(d) { - bounds.extend(getLL(d)); - }); - - // create the map once - if (!map) { - mapboxgl.accessToken = TOKEN; - map = new mapboxgl.Map({ - container: innerDiv[0][0], - // not allowed to whitelabel the map without enterprise license - // attributionControl: false, - style: 'mapbox://styles/mapbox/light-v8', - center: bounds.getCenter() - }); - map.dragPan.enable(); - map.scrollZoom.enable(); - map.boxZoom.disable(); - - // Add zoom and rotation controls to the map - map.addControl(new mapboxgl.Navigation({position: 'top-left'})); - - // Setup our svg layer that we can manipulate with d3 - var container = map.getCanvasContainer(); - svg = d3.select(container).append('svg'); - - circleControl = new CircleSelector(svg) - .projection(project) - .inverseProjection(function(a) { - return map.unproject({x: a[0], y: a[1]}); - }); - - // when lasso changes, update point selections - circleControl.on('update', function() { - svg.selectAll('circle.dot').style({ - fill: function(d) { - var thisDist = circleControl.distance(d); - var circleDist = circleControl.distance(); - if (thisDist < circleDist) { - return SELECTED_COLOR; - } - return UNSELECTED_COLOR; - } - }); - }); - circleControl.on('clear', function() { - svg.selectAll('circle.dot').style('fill', UNSELECTED_COLOR); - }); - - /* eslint no-inner-declarations: 0 */ - function render() { - // update points - dots.attr({ - cx: function(d) { - var x = project(d).x; - return x; - }, - cy: function(d) { - var y = project(d).y; - return y; - } - }); - // update circle - circleControl.update(svg); - } - - // re-render our visualization whenever the view changes - map.on('viewreset', function() { - render(); - }); - map.on('move', function() { - render(); - }); - } // end if (!map) ... - - // draw data points - dots = svg.selectAll('circle.dot') - .data(data); - dots.enter().append('circle').classed('dot', true) - .attr('r', 4) - .style({ - fill: UNSELECTED_COLOR, - stroke: 'white', - 'stroke-opacity': 0.6, - 'stroke-width': 1 - }); - - /* eslint no-undef: 0 */ - render(); - _.defer(function() { - map.resize(); - map.fitBounds(bounds, { - linear: true, - padding: 20 - }); - }); - }); // end selection.each() - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - chart.geoSelection = function(value) { - if (!value) { - circleControl.clear(); - return; - } - circleControl.setCircle(value[0], value[1]); - }; - - return chart; -}; - -module.exports = minicharts_d3fns_geo; diff --git a/src/app/minicharts/d3fns/index.js b/src/app/minicharts/d3fns/index.js deleted file mode 100644 index 509939d829a..00000000000 --- a/src/app/minicharts/d3fns/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - number: require('./number'), - boolean: require('./boolean'), - date: require('./date'), - string: require('./string'), - objectid: require('./date'), - geo: require('./geo'), // google maps - coordinates: require('./coordinates') -}; diff --git a/src/app/minicharts/d3fns/many.js b/src/app/minicharts/d3fns/many.js deleted file mode 100644 index 6c7ce6c8ee3..00000000000 --- a/src/app/minicharts/d3fns/many.js +++ /dev/null @@ -1,386 +0,0 @@ -/* eslint no-use-before-define: 0, camelcase: 0 */ -var d3 = require('d3'); -var $ = require('jquery'); -var _ = require('lodash'); -var shared = require('./shared'); - -var tooltipTemplate = require('./tooltip.jade'); -// var debug = require('debug')('mongodb-compass:minicharts:many'); - -require('../d3-tip')(d3); - -var minicharts_d3fns_many = function() { - // --- beginning chart setup --- - var width = 400; // default width - var height = 100; // default height - var options = { - view: null, - bgbars: false, - scale: false, - labels: false, // label defaults will be set further below - selectable: true // setting to false disables query builder for this chart - }; - - var xScale = d3.scale.ordinal(); - var yScale = d3.scale.linear(); - var labelScale = d3.scale.ordinal(); - - // set up tooltips - var tip = d3.tip() - .attr('class', 'd3-tip') - .direction('n') - .offset([-9, 0]); - - var brush = d3.svg.brush() - .x(xScale) - .on('brushstart', brushstart) - .on('brush', brushed) - .on('brushend', brushend); - // --- end chart setup --- - - function handleClick(d) { - if (!options.view || !options.selectable) { - return; - } - var evt = { - d: d, - self: this, - evt: d3.event, - type: 'click', - source: 'many' - }; - options.view.trigger('querybuilder', evt); - } - - function brushstart(clickedBar) { - // remove selections and half selections - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - bars.classed('half', false); - bars.classed('selected', function() { - return this === clickedBar; - }); - bars.classed('unselected', function() { - return this !== clickedBar; - }); - } - - function brushed() { - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - // var numSelected = options.view.queryAll('rect.selectable.selected').length; - var s = brush.extent(); - - bars.classed('selected', function(d) { - var left = xScale(d.label); - var right = left + xScale.rangeBand(); - return s[0] <= right && left <= s[1]; - }); - bars.classed('unselected', function(d) { - var left = xScale(d.label); - var right = left + xScale.rangeBand(); - return s[0] > right || left > s[1]; - }); - - if (!options.view) { - return; - } - var openLeft = d3.mouse(this)[0] <= 0; - var openRight = d3.mouse(this)[0] >= width; - // number of selected items has changed, trigger querybuilder event - var evt = { - type: 'drag', - source: 'many', - openLeft: openLeft, - openRight: openRight - }; - options.view.trigger('querybuilder', evt); - } - - function brushend() { - var bars = d3.selectAll(options.view.queryAll('rect.selectable')); - if (brush.empty()) { - bars.classed('selected', false); - bars.classed('unselected', false); - } - d3.select(this).call(brush.clear()); - - if (!options.view) { - return; - } - var openLeft = d3.mouse(this)[0] <= 0; - var openRight = d3.mouse(this)[0] >= width; - var evt = { - type: 'drag', - source: 'many', - openLeft: openLeft, - openRight: openRight - }; - options.view.trigger('querybuilder', evt); - } - - function handleMouseDown() { - if (!options.selectable) { - return; - } - var bar = this; - var parent = $(this).closest('.minichart'); - var background = parent.find('g.brush > rect.background')[0]; - var brushNode = parent.find('g.brush')[0]; - var start = d3.mouse(background)[0]; - var brushstartOnce = _.once(function() { - brushstart.call(brushNode, bar); - }); - - var w = d3.select(window) - .on('mousemove', mousemove) - .on('mouseup', mouseup); - - d3.event.preventDefault(); // disable text dragging - - function mousemove() { - brushstartOnce(); - var extent = [start, d3.mouse(background)[0]]; - d3.select(brushNode).call(brush.extent(_.sortBy(extent))); - brushed.call(brushNode); - } - - function mouseup() { - // bar.classed('selected', true); - w.on('mousemove', null).on('mouseup', null); - if (brush.empty()) { - // interpret as click - handleClick.call(bar, d3.select(bar).data()[0]); - } else { - brushend.call(brushNode); - } - } - } - - function chart(selection) { - selection.each(function(data) { - var values = _.pluck(data, 'count'); - var maxValue = d3.max(values); - var sumValues = d3.sum(values); - var percentFormat = shared.friendlyPercentFormat(maxValue / sumValues * 100); - var labels = options.labels; - var el = d3.select(this); - - xScale - .domain(_.pluck(data, 'label')) - .rangeBands([0, width], 0.3, 0.0); - - yScale - .domain([0, maxValue]) - .range([height, 0]); - - // set label defaults - if (options.labels) { - _.defaults(labels, { - 'text-anchor': function(d, i) { - if (i === 0) { - return 'start'; - } - if (i === data.length - 1) { - return 'end'; - } - return 'middle'; - }, - x: labels['text-anchor'] === 'middle' ? xScale.rangeBand() / 2 : function(d, i) { - if (i === 0) { - return 0; - } - if (i === data.length - 1) { - return xScale.rangeBand(); - } - return xScale.rangeBand() / 2; - }, - y: height + 5, - dy: '0.75em', - text: function(d) { - return d.count; - } - }); - } - - // setup tool tips - tip.html(function(d, i) { - if (typeof d.tooltip === 'function') { - return d.tooltip(d, i); - } - return d.tooltip || tooltipTemplate({ - label: shared.truncateTooltip(d.label), - count: percentFormat(d.count / sumValues * 100, false) - }); - }); - el.call(tip); - - // draw scale labels and lines if requested - if (options.scale) { - var triples = function(v) { - return [v, v / 2, 0]; - }; - - var scaleLabels = _.map(triples(maxValue / sumValues * 100), function(x) { - return percentFormat(x, true); - }); - - labelScale - .domain(scaleLabels) - .rangePoints([0, height]); - - var legend = el.selectAll('g.legend') - .data(scaleLabels); - - // create new legend elements - var legendEnter = legend.enter().append('g') - .attr('class', 'legend'); - - legendEnter - .append('text') - .attr('x', 0) - .attr('dx', '-1em') - .attr('dy', '0.3em') - .attr('text-anchor', 'end'); - - legendEnter - .append('line') - .attr('class', 'bg') - .attr('x1', -5) - .attr('y1', 0) - .attr('y2', 0); - - // update legend elements - legend - .attr('transform', function(d) { - return 'translate(0, ' + labelScale(d) + ')'; - }); - - legend.select('text') - .text(function(d) { - return d; - }); - - legend.select('line') - .attr('x2', width); - - legend.exit().remove(); - } - - if (options.selectable) { - var gBrush = el.selectAll('.brush').data([0]); - gBrush.enter().append('g') - .attr('class', 'brush') - .call(brush) - .selectAll('rect') - .attr('y', 0) - .attr('height', height); - } - - // select all g.bar elements - var bar = el.selectAll('.bar') - .data(data, function(d) { - return d.label; // identify data by its label - }); - - // create new bar elements as needed - var barEnter = bar.enter().append('g') - .attr('class', 'bar') - .attr('transform', function(d) { - return 'translate(' + xScale(d.label) + ', 0)'; - }); - - // if background bars are used, fill whole area with background bar color first - if (options.bgbars) { - barEnter.append('rect') - .attr('class', 'bg') - .attr('width', xScale.rangeBand()) - .attr('height', height); - } - - // now attach the foreground bars - barEnter - .append('rect') - .attr('class', options.selectable ? 'fg selectable' : 'fg') - .attr('x', 0) - .attr('width', xScale.rangeBand()); - - // create mouseover and click handlers - if (options.bgbars) { - // ... on a separate front "glass" pane if we use background bars - barEnter.append('rect') - .attr('class', 'glass') - .attr('width', xScale.rangeBand()) - .attr('height', height) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); - } else { - // ... or attach tooltips directly to foreground bars if we don't use background bars - barEnter.selectAll('.fg') - .on('mouseover', function(d) { - tip.show.call(tip, d); - }) - .on('mouseout', tip.hide); - - if (options.selectable) { - barEnter.selectAll('.selectable').on('mousedown', handleMouseDown); - } - } - - if (options.labels) { - barEnter.append('text') - .attr('x', labels.x) - .attr('dx', labels.dx) - .attr('y', labels.y) - .attr('dy', labels.dy) - .attr('text-anchor', labels['text-anchor']); - } - - - // now update _all_ bar elements (old and new) based on data - bar.selectAll('.fg') - .transition() - .attr('y', function(d) { - return yScale(d.count); - }) - .attr('height', function(d) { - return height - yScale(d.count); - }); - - if (options.labels) { - bar.select('text').text(labels.text); - } else { - bar.select('text').remove(); - } - - // finally remove obsolete bar elements - bar.exit().remove(); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_many; diff --git a/src/app/minicharts/d3fns/mapstyle.js b/src/app/minicharts/d3fns/mapstyle.js deleted file mode 100644 index ec925d33e52..00000000000 --- a/src/app/minicharts/d3fns/mapstyle.js +++ /dev/null @@ -1,89 +0,0 @@ -module.exports = [ - { - featureType: 'administrative', - elementType: 'all', - stylers: [ - { - visibility: 'simplified' - } - ] - }, - { - featureType: 'landscape', - elementType: 'geometry', - stylers: [ - { - visibility: 'simplified' - }, - { - color: '#fcfcfc' - } - ] - }, - { - featureType: 'poi', - elementType: 'all', - stylers: [ - { - visibility: 'off' - } - ] - }, - { - featureType: 'transit.station', - elementType: 'all', - stylers: [ - { - visibility: 'off' - } - ] - }, - { - featureType: 'road.highway', - elementType: 'geometry', - stylers: [ - { - visibility: 'simplified' - }, - { - color: '#dddddd' - } - ] - }, - { - featureType: 'road.arterial', - elementType: 'geometry', - stylers: [ - { - visibility: 'simplified' - }, - { - color: '#dddddd' - } - ] - }, - { - featureType: 'road.local', - elementType: 'geometry', - stylers: [ - { - visibility: 'simplified' - }, - { - color: '#eeeeee' - } - ] - }, - { - featureType: 'water', - elementType: 'geometry', - stylers: [ - { - visibility: 'simplified' - }, - { - color: '#dddddd' - } - ] - } -]; diff --git a/src/app/minicharts/d3fns/number.js b/src/app/minicharts/d3fns/number.js deleted file mode 100644 index 68d2981fa01..00000000000 --- a/src/app/minicharts/d3fns/number.js +++ /dev/null @@ -1,135 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var many = require('./many'); -var shared = require('./shared'); -// var debug = require('debug')('mongodb-compass:minicharts:number'); - -var minicharts_d3fns_number = function() { - var width = 400; - var height = 100; - var options = { - view: null - }; - var margin = shared.margin; - var xBinning = d3.scale.linear(); - var manyChart = many(); - - function chart(selection) { - selection.each(function(data) { - var grouped; - var el = d3.select(this); - var innerWidth = width - margin.left - margin.right; - var innerHeight = height - margin.top - margin.bottom; - - // transform data - if (options.model.unique < 20) { - grouped = _(data) - .groupBy(function(d) { - return d; - }) - .map(function(v, k) { - v.label = k; - v.x = parseFloat(k, 10); - v.value = v.x; - v.dx = 0; - v.count = v.length; - return v; - }) - .sortBy(function(v) { - return v.value; - }) - .value(); - } else { - // use the linear scale just to get nice binning values - xBinning - .domain(d3.extent(data)) - .range([0, innerWidth]); - - // Generate a histogram using approx. twenty uniformly-spaced bins - var ticks = xBinning.ticks(20); - var hist = d3.layout.histogram() - .bins(ticks); - - grouped = hist(data); - - _.each(grouped, function(d, i) { - var label; - if (i === 0) { - label = '< ' + (d.x + d.dx); - } else if (i === data.length - 1) { - label = '≥ ' + d.x; - } else { - label = d.x + '-' + (d.x + d.dx); - } - // remapping keys to conform with all other types - d.count = d.y; - d.value = d.x; - d.label = label; - }); - } - - var g = el.selectAll('g').data([grouped]); - - // append g element if it doesn't exist yet - g.enter() - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - var labels; - if (options.model.unique < 20) { - labels = true; - } else { - labels = { - text: function(d, i) { - if (i === 0) { - return 'min: ' + d3.min(data); - } - if (i === grouped.length - 1) { - return 'max: ' + d3.max(data); - } - return ''; - } - }; - } - - options.labels = labels; - options.scale = true; - - manyChart - .width(innerWidth) - .height(innerHeight - 10) - .options(options); - - g.call(manyChart); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_number; diff --git a/src/app/minicharts/d3fns/shared.js b/src/app/minicharts/d3fns/shared.js deleted file mode 100644 index 1d039bad895..00000000000 --- a/src/app/minicharts/d3fns/shared.js +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); - -// source: http://bit.ly/1Tc9Tp5 -function decimalPlaces(number) { - return ((+number).toFixed(20)).replace(/^-?\d*\.?|0+$/g, '').length; -} - -var minicharts_d3fns_shared = { - - margin: { - top: 10, - right: 0, - bottom: 10, - left: 40 - }, - - friendlyPercentFormat: function(vmax) { - var prec1Format = d3.format('.1r'); - var intFormat = d3.format('.0f'); - var format = vmax > 1 ? intFormat : prec1Format; - var maxFormatted = format(vmax); - var maxDecimals = decimalPlaces(maxFormatted); - - return function(v, incPrec) { - if (v === vmax) { - return maxFormatted + '%'; - } - if (v > 1 && !incPrec) { // v > vmax || maxFormatted % 2 === 0 - return d3.round(v, maxDecimals) + '%'; - } - // adjust for corrections, if increased precision required - return d3.round(v / vmax * maxFormatted, maxDecimals + 1) + '%'; - }; - }, - - truncateTooltip: function(text, maxLength) { - maxLength = maxLength || 500; - if (text.length > maxLength) { - text = text.substring(0, maxLength - 1) + '…'; - } - return text; - } - -}; -module.exports = minicharts_d3fns_shared; diff --git a/src/app/minicharts/d3fns/string.js b/src/app/minicharts/d3fns/string.js deleted file mode 100644 index 1a577edf7a0..00000000000 --- a/src/app/minicharts/d3fns/string.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint camelcase: 0 */ -var d3 = require('d3'); -var _ = require('lodash'); -var few = require('./few'); -var many = require('./many'); -var shared = require('./shared'); - -var minicharts_d3fns_string = function() { - // --- beginning chart setup --- - var width = 400; - var height = 100; - var options = { - view: null - }; - var fewChart = few(); - var manyChart = many(); - var margin = shared.margin; - // --- end chart setup --- - - function chart(selection) { - selection.each(function(data) { - var el = d3.select(this); - var innerWidth = width - margin.left - margin.right; - var innerHeight = height - margin.top - margin.bottom; - - // group into labels and values per bucket, sort descending - var grouped = _(data) - .groupBy(function(d) { - return d; - }) - .map(function(v, k) { - return { - label: k, - value: k, - count: v.length - }; - }) - .sortByOrder('count', [false]) // descending on value - .value(); - - - var g = el.selectAll('g').data([grouped]); - - // append g element if it doesn't exist yet - g.enter() - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') - .attr('width', innerWidth) - .attr('height', innerHeight); - - var chartFn = grouped.length <= 5 ? fewChart : manyChart; - options.scale = true; - - chartFn - .width(innerWidth) - .height(innerHeight) - .options(options); - - g.call(chartFn); - }); - } - - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - - chart.options = function(value) { - if (!arguments.length) { - return options; - } - _.assign(options, value); - return chart; - }; - - return chart; -}; - -module.exports = minicharts_d3fns_string; diff --git a/src/app/minicharts/d3fns/tooltip.jade b/src/app/minicharts/d3fns/tooltip.jade deleted file mode 100644 index e50c5321618..00000000000 --- a/src/app/minicharts/d3fns/tooltip.jade +++ /dev/null @@ -1,3 +0,0 @@ -.tooltip-wrapper - .tooltip-label!= label - .tooltip-value #{count} diff --git a/src/app/minicharts/document-root.jade b/src/app/minicharts/document-root.jade deleted file mode 100644 index 84c82648bc9..00000000000 --- a/src/app/minicharts/document-root.jade +++ /dev/null @@ -1,4 +0,0 @@ -div(data-hook='viz-container') - dl - dt Document with #{fieldsPluralized} - dd diff --git a/src/app/minicharts/document-root.js b/src/app/minicharts/document-root.js deleted file mode 100644 index e3c79957d8a..00000000000 --- a/src/app/minicharts/document-root.js +++ /dev/null @@ -1,15 +0,0 @@ -var VizView = require('./viz'); -var pluralize = require('pluralize'); - -var documentRootTemplate = require('./document-root.jade'); -// var debug = require('debug')('mongodb-compass:minicharts:document-root'); - -module.exports = VizView.extend({ - template: documentRootTemplate, - render: function() { - var fieldNames = this.model.parent.fields.pluck('name'); - this.renderWithTemplate({ - fieldsPluralized: pluralize('nested field', fieldNames.length, true) - }); - } -}); diff --git a/src/app/minicharts/index.js b/src/app/minicharts/index.js deleted file mode 100644 index 44deb244b34..00000000000 --- a/src/app/minicharts/index.js +++ /dev/null @@ -1,167 +0,0 @@ -var AmpersandView = require('ampersand-view'); -var _ = require('lodash'); -var raf = require('raf'); -var app = require('ampersand-app'); -var VizView = require('./viz'); -var UniqueMinichartView = require('./unique'); -var DocumentRootMinichartView = require('./document-root'); -var ArrayRootMinichartView = require('./array-root'); -var vizFns = require('./d3fns'); -var QueryBuilderMixin = require('./querybuilder'); -var Collection = require('ampersand-collection'); -var metrics = require('mongodb-js-metrics')(); -var navigator = window.navigator; - -var minichartTemplate = require('./minichart.jade'); - -// var debug = require('debug')('mongodb-compass:minicharts:index'); - -var ArrayCollection = Collection.extend({ - model: Array -}); - -/** - * a wrapper around VizView to set common default values - */ -module.exports = AmpersandView.extend(QueryBuilderMixin, { - modelType: 'MinichartView', - template: minichartTemplate, - session: { - subview: 'state', - viewOptions: 'object', - selectedValues: { - type: 'array', - default: function() { - return []; - } - } - }, - initialize: function(opts) { - // setting some defaults for minicharts - this.viewOptions = _.defaults(opts, { - width: 440, - height: 100, - renderMode: 'svg', - className: 'minichart', - debounceRender: false, - vizFn: vizFns[opts.model.getId().toLowerCase()] || null - }); - this.listenTo(app.volatileQueryOptions, 'change:query', this.handleVolatileQueryChange); - }, - _mangleGeoCoordinates: function(values) { - // now check value bounds - var lons = values.filter(function(val, idx) { - return idx % 2 === 0; - }); - var lats = values.filter(function(val, idx) { - return idx % 2 === 1; - }); - if (_.min(lons) >= -180 && _.max(lons) <= 180 && _.min(lats) >= -90 && _.max(lats) <= 90) { - return new ArrayCollection(_.zip(lons, lats)); - } - return false; - }, - /* eslint complexity: 0 */ - _geoCoordinateTransform: function() { - var coords; - if (this.model.name === 'Coordinates') { - // been here before, don't need to do it again - return true; - } - var fields = this.model.fields; - if (this.model.name === 'Document') { - if (fields.length !== 2 - || !fields.get('type') - || !fields.get('coordinates') - || fields.get('type').type !== 'String' - || fields.get('type').types.get('String').unique !== 1 - || fields.get('type').types.get('String').values.at(0).value !== 'Point' - || fields.get('coordinates').types.get('Array').count - !== fields.get('coordinates').count - || fields.get('coordinates').types.get('Array').average_length !== 2 - || fields.get('coordinates').types.get('Array').types.get('Number') === undefined - ) { - return false; - } - coords = this._mangleGeoCoordinates( - this.model.fields.get('coordinates').types.get('Array') - .types.get('Number').values.serialize()); - if (!coords) { - return false; - } - // we have a GeoJSON document: {type: "Point", coordinates: [lng, lat]} - this.model.values = coords; - this.model.fields.reset(); - this.model.name = 'Coordinates'; - return true; - } else if (this.model.name === 'Array') { - var lengths = this.model.lengths; - if (_.min(lengths) !== 2 || _.max(lengths) !== 2) { - return false; - } - // make sure the array only contains numbers, otherwise not coords - if (this.model.types.length !== 1 - || this.model.types.at(0).name !== 'Number') { - return false; - } - coords = this._mangleGeoCoordinates( - this.model.types.get('Number').values.serialize()); - if (!coords) { - return false; - } - // we have a legacy coordinate pair: [lng, lat] - this.model.values = coords; - this.model.types.reset(); - this.model.name = 'Coordinates'; - return true; - } - return false; - }, - render: function() { - this.renderWithTemplate(this); - this._geoCoordinateTransform(); - - if (this.model.name === 'Coordinates') { - metrics.track('Geo Data', 'detected'); - // check if we can load google maps or if we need to fall back to - // a simpler coordinate chart - if (app.isFeatureEnabled('enableMaps') - && navigator.onLine) { - this.viewOptions.renderMode = 'html'; - this.viewOptions.height = 300; - this.viewOptions.vizFn = vizFns.geo; - this.subview = new VizView(this.viewOptions); - } else { - // we have coordinates but cannot load google maps (offline, invalid - // key, etc.). Fall back to simplified coordinate chart. - this.viewOptions.renderMode = 'svg'; - this.viewOptions.height = 300; - this.viewOptions.vizFn = vizFns.coordinates; - this.subview = new VizView(this.viewOptions); - } - } else if (['String', 'Number'].indexOf(this.model.name) !== -1 - && !this.model.has_duplicates) { - // unique values get a div-based UniqueMinichart - this.viewOptions.renderMode = 'html'; - this.viewOptions.vizFn = null; - this.viewOptions.className = 'minichart unique'; - this.subview = new UniqueMinichartView(this.viewOptions); - } else if (this.model.name === 'Document') { - this.viewOptions.height = 55; - this.subview = new DocumentRootMinichartView(this.viewOptions); - } else if (this.model.name === 'Array') { - this.viewOptions.height = 55; - this.subview = new ArrayRootMinichartView(this.viewOptions); - } else { - // otherwise, create a svg-based VizView for d3 - this.subview = new VizView(this.viewOptions); - } - - if (app.isFeatureEnabled('queryBuilder')) { - this.listenTo(this.subview, 'querybuilder', this.handleQueryBuilderEvent); - } - raf(function() { - this.renderSubview(this.subview, this.queryByHook('minichart')); - }.bind(this)); - } -}); diff --git a/src/app/minicharts/index.less b/src/app/minicharts/index.less deleted file mode 100644 index 9e201684a46..00000000000 --- a/src/app/minicharts/index.less +++ /dev/null @@ -1,336 +0,0 @@ -// minicharts -@mc-blue0: #43B1E5; -@mc-blue1: lighten(@mc-blue0, 7.5%); -@mc-blue2: lighten(@mc-blue0, 15%); -@mc-blue3: lighten(@mc-blue0, 22.5%); -@mc-blue4: lighten(@mc-blue0, 30%); -@mc-blue5: lighten(@mc-blue0, 37.5%); - -@mc-bg: @gray8; -@mc-fg: @mc-blue0; -@mc-fg-selected: @chart0; -@mc-fg-unselected: @gray6; - -div.minichart.unique { - font-size: 12px; - - dl.dl-horizontal { - margin-left: -32px; - padding-top: 12px; - max-height: 112px; - overflow: hidden; - - dt { - color: @gray3; - width: 20px; - - a { - color: @gray5; - display: inline-block; - - &:hover { - text-decoration: none; - color: @gray1; - } - } - i.mms-icon-continuous { // remove after wrapping this in an again - color: @gray5; - cursor: pointer; - - &:hover { - color: @gray1; - } - } - } - dd { - margin-left: 30px; - overflow: hidden; - - ul li { - margin-bottom: 6px; - - code { - cursor: pointer; - background-color: @gray7; - border: 1px solid transparent; - color: @gray1; - font-size: 12px; - line-height: 20px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - } - - code.selected { - background-color: @mc-fg-selected; - color: @pw; - // border: 1px dotted @gray2; - } - } - } - } -} - -.minichart-wrapper { - svg.minichart { - margin-left: -40px; - } -} - -.layer, .layer svg { - position: absolute; -} - -.layer svg.marker { - width: 20px; - height: 20px; - - circle { - fill: @mc-fg; - stroke: @pw; - stroke-width: 1.5px; - - &.selected { - fill: @mc-fg-selected; - } - } -} - -.layer svg.selection { - visibility: hidden; - - circle { - fill: @mc-fg-selected; - fill-opacity:0.2; - stroke: @mc-fg-selected; - stroke-width: 2px; - } -} - -svg.minichart { - font-size: 10px; - - text { - fill: @gray4; - font-weight: bold; - } - - .glass { - opacity: 0; - } - - g.brush rect.extent { - fill: @mc-fg-selected; - fill-opacity:0.2; - } - - .hour, .weekday { - .bar { - cursor: default !important; - } - } - - .bar { - shape-rendering: crispEdges; - cursor: crosshair; - - rect.bg { - fill: @mc-bg; - } - - rect.fg { - fill: @mc-fg; - - &.selected { - fill: @mc-fg-selected; - } - - &.half-selected { - fill: @mc-fg-selected; - mask: url(#mask-stripe); - } - - &.unselected { - fill: @mc-fg-unselected; - } - } - - &.few { - - rect { - stroke: white; - stroke-width: 2px; - } - - rect.fg-0 { - fill: @mc-blue0; - } - - rect.fg-1 { - fill: @mc-blue1; - } - - rect.fg-2 { - fill: @mc-blue2; - } - - rect.fg-3 { - fill: @mc-blue3; - } - - rect.fg-4 { - fill: @mc-blue4; - } - - rect.fg-5 { - fill: @mc-blue5; - } - - rect.fg.selected { - fill: @mc-fg-selected; - } - rect.fg.unselected { - fill: @mc-fg-unselected; - } - - text { - fill: white; - font-size: 12px; - } - } - } - - .line { - stroke: @mc-fg; - - &.selected { - stroke: @mc-fg-selected; - } - } - - .legend { - text { - fill: @gray5; - } - - line { - stroke: @gray7; - } - shape-rendering: crispEdges; - } - - .axis path, .axis line { - fill: none; - stroke: @gray7; - shape-rendering: crispEdges; - } - - .circle { - fill: @mc-fg; - stroke: @pw; - stroke-width: 1.5px; - - &.selected { - fill: @mc-fg-selected; - } - } -} - -.tooltip-wrapper { - line-height: 120%; - max-width: 400px; -} - -.map { - position:absolute; - top:0; - bottom:0; - width:100%; - float: left; - svg { - position: absolute; - width: 100%; - height: 100%; - } - nav { - position: absolute; - top: 40px; - left: 20px; - z-index: 1; - } - #circle { - background-color: rgba(20, 20, 20, 0.1); - font-family: Helvetica, sans-serif; - color: #3b83bd; - padding: 5px 8px; - border-radius: 3px; - cursor: pointer; - border: 1px solid #111; - } - #circle.active { - background-color: rgba(250, 250, 250, 0.9); - } - i.help { - display: inline-block; - float: left; - } -} - -// -- d3-tip styling -.d3-tip { - z-index: 2; - line-height: 1; - padding: 8px; - background: #000; - color: #fff; - border-radius: 5px; - pointer-events: none; - font-size: 12px; -} - -/* Creates a small triangle extender for the tooltip */ -.d3-tip:after { - box-sizing: border-box; - display: inline; - font-size: 14px; - width: 100%; - line-height: 1; - color: #000; - position: absolute; - pointer-events: none; -} - -/* Northward tooltips */ -.d3-tip.n:after { - content: "\25BC"; - margin: -4px 0 0 0; - top: 100%; - left: 0; - text-align: center; -} - -/* Eastward tooltips */ -.d3-tip.e:after { - content: "\25C0"; - margin: -4px 0 0 0; - top: 50%; - left: -8px; -} - -/* Southward tooltips */ -.d3-tip.s:after { - content: "\25B2"; - margin: 0 0 1px 0; - top: -8px; - left: 0; - text-align: center; -} - -/* Westward tooltips */ -.d3-tip.w:after { - content: "\25B6"; - margin: -4px 0 0 -1px; - top: 50%; - left: 100%; -} diff --git a/src/app/minicharts/minichart.jade b/src/app/minicharts/minichart.jade deleted file mode 100644 index e01635af0b4..00000000000 --- a/src/app/minicharts/minichart.jade +++ /dev/null @@ -1 +0,0 @@ -div(data-hook='minichart', class='minichart-wrapper') diff --git a/src/app/minicharts/querybuilder.js b/src/app/minicharts/querybuilder.js deleted file mode 100644 index c2dbd6bb7a1..00000000000 --- a/src/app/minicharts/querybuilder.js +++ /dev/null @@ -1,634 +0,0 @@ -/* eslint indent:0 */ -var _ = require('lodash'); -var $ = require('jquery'); -var d3 = require('d3'); -var app = require('ampersand-app'); -var LeafValue = require('mongodb-language-model').LeafValue; -var LeafClause = require('mongodb-language-model').LeafClause; -var ListOperator = require('mongodb-language-model').ListOperator; -var ValueOperator = require('mongodb-language-model').ValueOperator; -var GeoOperator = require('mongodb-language-model').GeoOperator; -var Range = require('mongodb-language-model').helpers.Range; -var metrics = require('mongodb-js-metrics')(); - -// var debug = require('debug')('mongodb-compass:minicharts:querybuilder'); - -var MODIFIERKEY = 'shiftKey'; -var checkBounds = { - $gte: function(a, b) { - return a >= b; - }, - $gt: function(a, b) { - return a > b; - }, - $lte: function(a, b) { - return a <= b; - }, - $lt: function(a, b) { - return a < b; - } -}; - -module.exports = { - /** - * Handles query builder events, routing them to the appropriate specific handler methods - * @param {Object} data contains information about the event. - * - * For `click` events, data looks like this: - * { - * d: the data point - * self: the dom element itself - * evt: the event object - * type: 'click' - * source: where the event originated, currently 'few', 'many', 'unique', 'date' - * } - * - * For `drag` events, data looks like this: - * { - * selected: array of selected values - * type: 'click', - * source: where the event originated, currently 'many', 'date' - * } - * - */ - /* eslint complexity: 0 */ - handleQueryBuilderEvent: function(data) { - var queryType; - - if (data.type === 'click') { - data.evt.stopPropagation(); - data.evt.preventDefault(); - } - - // defocus currently active element (likely the refine bar) so it can update the query - $(document.activeElement).blur(); - - // determine what kind of query this is (distinct, range, geo, ...) - switch (this.model.getType()) { - case 'Boolean': // fall-through to String - case 'String': - queryType = 'distinct'; - break; - case 'Number': - if (data.source === 'unique') { - queryType = 'distinct'; - } else { - queryType = 'range'; - } - break; - case 'ObjectID': // fall-through to Date - case 'Date': - queryType = 'range'; - break; - case 'Array': - case 'Document': - case 'Coordinates': - if (data.source === 'geo') { - queryType = 'geo'; - } else { - throw new Error('unsupported querybuilder type ' + this.model.getType()); - } - break; - default: // @todo other types not implemented yet - throw new Error('unsupported querybuilder type ' + this.model.getType()); - } - - // now call appropriate event handlers and query build methods - var message = { - data: data - }; - - if (data.type === 'drag' || data.type === 'geo') { - message = this['updateSelection_' + data.type](message); - message = this['buildQuery_' + queryType](message); - } else { - message = this['updateSelection_' + queryType](message); - message = this['buildQuery_' + queryType](message); - message = this['updateUI_' + queryType](message); - } - this.updateVolatileQuery(message); - - // track query builder usage with type,but at most once per sec - this.trackMetrics('Query Builder', 'used', { - type: this.model.getType(), - unique: data.source === 'unique', - mode: queryType - }); - }, - trackMetrics: _.debounce(metrics.track.bind(metrics), 1000), - /** - * adds `selected` for distinct query builder events, e.g. string and unique - * type. Single click selects individual element, shift-click adds to selection. - * - * For distinct-type minicharts, `selected` contains an entry for each selected value. - * - * @param {Object} message message with key: data - * @return {Object} message message with keys: data, selected - */ - updateSelection_distinct: function(message) { - if (!message.data.evt[MODIFIERKEY]) { - if (this.selectedValues.length === 1 && this.selectedValues[0] === message.data.d.value) { - // case where 1 element is selected and it is clicked again (need to unselect) - this.selectedValues = []; - } else { - // case where multiple or no elements are selected (need to select that one item) - this.selectedValues = [message.data.d.value]; - } - } else if (_.contains(this.selectedValues, message.data.d.value)) { - // case where selected element is shift-clicked (need to remove from selection) - _.remove(this.selectedValues, function(d) { - return d === message.data.d.value; - }); - } else { - // case where unselected element is shift-clicked (need to add to selection) - this.selectedValues.push(message.data.d.value); - } - message.selected = this.selectedValues; - return message; - }, - - /** - * adds `selected` and `dx` for range query builder events, e.g. number, date, objectid type. - * single click selects individual element, shift-click extends to range (the single click is - * interpreted as one end of the range, shift-click as the other). - * - * For range-type minicharts, `selected` contains up to two values, the lower and upper bound. - * The 0th value is the one selected via regular click, the 1st value is the shift-clicked one. - * - * @param {Object} message message with key: data - * @return {Object} message message with keys: data, selected, dx - */ - updateSelection_range: function(message) { - var definedValues = _.filter(this.selectedValues, function(i) { - return i !== undefined; - }); - if (definedValues.length === 1 && definedValues[0] === message.data.d.value) { - // case where single selected item is clicked again (need to unselect) - this.selectedValues = []; - } else if (message.data.evt[MODIFIERKEY]) { - // shift-click modifies the value at index 1 - this.selectedValues[1] = message.data.d.value; - } else { - // case where multiple or no elements are selected (need to just select one item) - this.selectedValues = [message.data.d.value]; - } - message.dx = message.data.d.dx || 0; - message.selected = this.selectedValues; - - return message; - }, - /** - * updates `selected` for query builder events created with a click-drag mouse action. - * The visual updates are handled by d3 directly, so all we have to do is update the selected - * values based on the selected elements. Need to get dx from one of the selected bars as we - * don't get it through the message.data object like for individual clicks. - * - * @param {Object} message message with key: data - * @return {Object} message message with keys: data, selected, dx - */ - updateSelection_drag: function(message) { - var data = d3.selectAll(this.queryAll('.selectable.selected')).data(); - message.dx = _.has(data[0], 'dx') ? data[0].dx : 0; - this.selectedValues = _.pluck(data, 'value'); - message.selected = this.selectedValues; - - return message; - }, - /** - * updates `selected` for query builder events created on a geo map. - * The visual updates are handled by d3 directly, selected will just contain the center - * longitude, latitude and distance in miles. - * - * @param {Object} message message with key: data - * @return {Object} message message with keys: data, selected - */ - updateSelection_geo: function(message) { - if (!message.data.center || !message.data.distance) { - this.selectedValues = []; - } else { - this.selectedValues = [ - message.data.center[0], - message.data.center[1], - message.data.distance / 3963.2 // equatorial radius of earth in miles - ]; - } - message.selected = this.selectedValues; - return message; - }, - /** - * build new distinct ($in) query based on current selection - * - * @param {Object} message message with keys: data, selected - * @return {Object} message message with keys: data, selected, value, elements, op - */ - buildQuery_distinct: function(message) { - message.elements = this.queryAll('.selectable'); - message.op = '$in'; - - // build new value - if (message.selected.length === 0) { - // no value - message.value = null; - } else if (message.selected.length === 1) { - // single value - message.value = new LeafValue(message.selected[0], { - parse: true - }); - } else { - // multiple values - message.value = new ListOperator({ - $in: message.selected.map(function(el) { - return el; - }) - }, { - parse: true - }); - } - return message; - }, - - /** - * build new range ($gte, $lt(e)) query based on current selection and store in clause. - * If the UI element represents a range (i.e. binned histograms where one bar represents - * 20-30, the next one 30-40, etc.) then the upper limit is non-inclusive ($lt). - * If however the UI elements represents a single number, then the upper limit is - * inclusive ($lte). This is indicated by the d.dx variable, which is only > 0 for binned ranges. - * - * @param {Object} message message with keys: data, selected, dx - * @return {Object} message message with keys: data, selected, value, elements, lowerOp - * upperOp, dx - */ - buildQuery_range: function(message) { - message.selected = this._getRangeExtent(message.selected); - message.elements = this.queryAll('.selectable'); - - // handle empty selection - if (message.selected.length === 0) { - message.value = null; - return message; - } - - if (message.selected.length === 1) { - if (message.dx > 0) { - // binned values, turn single selection into a range - message.selected[1] = message.selected[0] + message.dx; - } else { - message.value = new LeafValue({ - content: message.selected[0] - }); - return message; - } - } else if (message.selected.length === 2) { - if (message.dx > 0) { - // binned values, we need to increase the upper bound by the bin size - message.selected[1] += message.dx; - } - } else { - // should never end up here - throw new Error('message.selected should never be longer than 2 elements here!'); - } - - var q; - // in case one of the bounds is open - if (message.data.openLeft) { - message.upperOp = message.dx > 0 ? '$lt' : '$lte'; - q = {}; - q[message.upperOp] = message.selected[1]; - message.value = new ValueOperator(q, {parse: true}); - return message; - } - - // in case one of the bounds is open - if (message.data.openRight) { - message.lowerOp = '$gte'; - message.value = new ValueOperator({$gte: message.selected[0]}, {parse: true}); - return message; - } - - // at this point we definitely have 2 selected values to build a range - message.lowerOp = '$gte'; - message.upperOp = message.dx > 0 ? '$lt' : '$lte'; - - // in upwards pass, increase upper bound according to bin size - message.value = new Range(message.selected[0], message.selected[1], message.dx === 0); - - return message; - }, - - /** - * build new geo ($geoWithin) query based on current selection - * - * @param {Object} message message with keys: data, selected - * @return {Object} message message with keys: data, selected, value, elements, op - */ - buildQuery_geo: function(message) { - message.elements = this.queryAll('.selectable'); - message.op = '$geoWithin'; - - // build new value - if (message.selected.length === 0) { - // no value - message.value = null; - } else { - // multiple values - message.value = new GeoOperator({ - $geoWithin: { - $centerSphere: [[message.selected[0], message.selected[1]], message.selected[2]] - } - }, { - parse: true - }); - } - return message; - }, - - /** - * update the UI after a distinct query and mark appropriate elements with .select class. - * - * @param {Object} message message with keys: data, selected, value, elements - * @return {Object} message no changes on message, just pass it through for consistency - */ - updateUI_distinct: function(message) { - // in case message was not submitted (e.g. from unique minicharts), reconstruct it here - if (!message) { - message = { - selected: this.selectedValues, - elements: this.queryAll('.selectable') - }; - } - _.each(message.elements, function(el) { - var elData; - if (el.innerText !== undefined) { - elData = el.innerText; - } else { - elData = d3.select(el).data(); - if (elData.length && elData[0].value !== undefined) { - elData = elData[0].value; - } else { - return message; - } - } - if (this.model.getType() === 'Number') { - elData = parseFloat(elData, 10); - } - if (_.contains(message.selected, elData)) { - el.classList.add('selected'); - el.classList.remove('unselected'); - } else { - el.classList.remove('selected'); - if (message.selected.length === 0) { - el.classList.remove('unselected'); - } else { - el.classList.add('unselected'); - } - } - }.bind(this)); - return message; - }, - - /** - * update the UI after a range query and mark appropriate elements with .select class. - * - * @param {Object} message message with keys: data, selected, value, elements, lowerOp, - * upperOp, dx - * @return {Object} message no changes on message, just pass it through for consistency - */ - updateUI_range: function(message) { - // remove `.selected` and `.half` classes from all elements - _.each(message.elements, function(el) { - el.classList.remove('selected'); - el.classList.remove('half'); - if (message.selected.length === 0) { - el.classList.remove('unselected'); - } else { - el.classList.add('unselected'); - } - }); - if (message.selected.length > 0) { - var getOrderedValueHelper = this._getOrderedValueHelper.bind(this); - _.each(message.elements, function(el) { - var d = getOrderedValueHelper(d3.select(el).data()[0]); - var mustSelect = false; - var halfSelect; - if (message.selected.length === 1) { - // handle single selection of non-binned data - mustSelect = d.value === message.selected[0]; - } else if (message.dx === 0) { - // handle non-binned ranges - mustSelect = checkBounds[message.lowerOp](d.value, message.selected[0]) - && checkBounds[message.upperOp](d.value, message.selected[1]); - } else { - // handle binned ranges - halfSelect = false; - /** - * 4 cases to consider: upper/lower bound x hit bound exactly/not - */ - if (d.value === message.selected[0]) { - // lower bound exact match - mustSelect = true; - halfSelect = message.lowerOp === '$gt'; - } else { - // lower bound no exact match - mustSelect = d.value + message.dx > message.selected[0]; - halfSelect = d.value < message.selected[0]; - } - if (d.value === message.selected[1]) { - // upper bound exact match - mustSelect = mustSelect && message.upperOp === '$lte'; - halfSelect = true; - } else { - // upper bound no exact match - mustSelect = mustSelect && d.value < message.selected[1]; - halfSelect = halfSelect || d.value + message.dx > message.selected[1]; - } - // mustSelect = lowerSelect && upperSelect; - } - if (mustSelect) { - el.classList.add('selected'); - if (halfSelect) { - el.classList.add('half'); - } - el.classList.remove('unselected'); - } - }); - } - return message; - }, - /** - * update the UI after a geo query was manipulated in the refine bar. - * - * @param {Object} message message with keys: data, selected, value - * @return {Object} message no changes on message, just pass it through for consistency - */ - updateUI_geo: function(message) { - if (this.selectedValues && this.selectedValues.length) { - // convert radius back to miles - var params = this.selectedValues.slice(); - params[1] *= 3963.2; - this.subview.chart.geoSelection(params); - } else { - this.subview.chart.geoSelection(null); - } - return message; - }, - /** - * Query Builder upwards pass - * The user interacted with the minichart to build a query. We need to ask the volatile query - * if a relevant clause exists already, and replace the value, or create a new clause. In the - * case of an empty selection, we need to potentially restore the previous original value. - * - * @param {Object} message message with keys: data, selected, value, lowerOp, upperOp, - * elements, dx - */ - updateVolatileQuery: function(message) { - var query = app.volatileQueryOptions.query; - var clause = query.clauses.get(this.model.path); - if (clause) { - if (message.value !== null) { - clause.value = message.value; - } else { - // no selection on this minichart, try to restore previous value - var previousClause = app.queryOptions.query.clauses.get(this.model.path); - if (!previousClause) { - query.clauses.remove(clause); - } else { - clause.value = previousClause.value; - } - } - } else if (message.value !== null) { - clause = new LeafClause(); - clause.key.content = this.model.path; - clause.value = message.value; - query.clauses.add(clause); - } - }, - - /** - * Query Builder downwards pass - * The query was changed in the refine bar, we need to see if there is a - * relevant clause for this minichart, and update the UI accordingly. If there isn't one, we - * pass `undefined` to processValue so the selection gets cleared. - * - * @param {QueryOptions} volatileQueryOptions the volatile query options (not needed) - * @param {Query} query the new query - */ - handleVolatileQueryChange: function(volatileQueryOptions, query) { - var clause = query.clauses.get(this.model.path); - if (!clause) { - this.processValue(); - } else { - this.processValue(clause.value); - } - }, - /** - * pushes a new value downstream, which originates from manual changes in the refine bar. - * This function updates selectedValues based on the value. - * - * @param {Value} value value pushed down from refine bar for this minichart - */ - processValue: function(value) { - if (!this.rendered) { - return; - } - - var message = { - elements: this.queryAll('.selectable') - }; - - if (!value || !value.valid) { - this.selectedValues = []; - message.selected = this.selectedValues; - if (_.has(this.subview.chart, 'geoSelection')) { - this.updateUI_geo(message); - } else { - // updateUI_distinct will do the right thing here and clear any selection, - // even in the case where the minichart is a range type. - this.updateUI_distinct(message); - } - return; - } - if (value.className === 'LeafValue') { - this.selectedValues = [value.buffer]; - message.selected = this.selectedValues; - this.updateUI_distinct(message); - return; - } else if (value.className === 'OperatorObject') { - var inOperator = value.operators.get('$in'); - if (inOperator) { - // case: $in query - this.selectedValues = inOperator.values.serialize(); - message.selected = this.selectedValues; - this.updateUI_distinct(message); - return; - } - var geoOperator = value.operators.get('$geoWithin'); - if (geoOperator) { - // case: $geoWithin query - this.selectedValues = geoOperator.shape.parameters; - message.selected = this.selectedValues; - this.updateUI_geo(message); - return; - } - if (['$gt', '$lt', '$gte', '$lte'].indexOf(value.operators.at(0).operator) !== -1) { - // case: range query - this.selectedValues = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY]; - message.lowerOp = '$gt'; - message.upperOp = '$lt'; - value.operators.each(function(operator) { - if (_.startsWith(operator.operator, '$gt')) { - this.selectedValues[0] = operator.value.buffer; - message.lowerOp = operator.operator; - } else if (_.startsWith(operator.operator, '$lt')) { - this.selectedValues[1] = operator.value.buffer; - message.upperOp = operator.operator; - } else { - // unsupported case, ignore - } - }.bind(this)); - // get dx from a selectable dom element - var data = d3.selectAll(this.queryAll('.selectable')).data(); - message.dx = _.has(data[0], 'dx') ? data[0].dx : 0; - message.selected = this.selectedValues; - this.updateUI_range(message); - return; - } - } else { - // unsupported case, ignore - } - }, - - /** - * Extract a value that can be ordered (e.g. number, date, ...) - * @param {Object} d event data object triggered by the minichart - * @return {Any} value to be returned that can be used for comparisons < and > - */ - _getOrderedValueHelper: function(d) { - if (!d || !d._bsontype) { - return d; - } - return d._bsontype === 'ObjectID' ? d.getTimestamp() : d; - }, - /** - * takes a selection of elements and returns a copy containing at most 2 elements. If the - * original selection length was less than two, a copy of the original selection is returned. - * If the original selection length was 2 or more, return an array with the min and the max. - * @param {Array} selection the original selection - * @return {Array} array of at most 2 values, min and max of the original selection - */ - _getRangeExtent: function(selection) { - if (selection.length < 2) { - return selection.slice(); - } - var getOrderedValueHelper = this._getOrderedValueHelper.bind(this); - var lower = _.min(selection, function(el) { - return getOrderedValueHelper(el); - }); - var upper = _.max(selection, function(el) { - return getOrderedValueHelper(el); - }); - if (lower === upper) { - return [lower]; - } - return [lower, upper]; - } -}; diff --git a/src/app/minicharts/svg-template.jade b/src/app/minicharts/svg-template.jade deleted file mode 100644 index 96ca6aa217d..00000000000 --- a/src/app/minicharts/svg-template.jade +++ /dev/null @@ -1,6 +0,0 @@ -svg(data-hook='viz-container') - defs - pattern(id='diagonal-stripes', width='4', height='4', patternUnits='userSpaceOnUse', patternTransform='rotate(45)') - rect(width='2.5', height='4', transform='translate(0,0)', fill='white') - mask(id='mask-stripe') - rect(x='0', y='0', width='100%', height='100%', fill='url(#diagonal-stripes)') diff --git a/src/app/minicharts/unique.jade b/src/app/minicharts/unique.jade deleted file mode 100644 index 0cd7106638d..00000000000 --- a/src/app/minicharts/unique.jade +++ /dev/null @@ -1,10 +0,0 @@ -div(data-hook='viz-container') - dl.dl-horizontal - dt - a - i.mms-icon-continuous(data-hook='refresh') - dd - ul.list-inline - each val in randomValues || [] - li.bubble - code.selectable= val diff --git a/src/app/minicharts/unique.js b/src/app/minicharts/unique.js deleted file mode 100644 index 842ff29a71a..00000000000 --- a/src/app/minicharts/unique.js +++ /dev/null @@ -1,73 +0,0 @@ -var VizView = require('./viz'); -var _ = require('lodash'); - -var uniqueTemplate = require('./unique.jade'); - -module.exports = VizView.extend({ - session: { - timer: { - type: 'number', - default: null - } - }, - template: uniqueTemplate, - derived: { - randomValues: { - cache: false, - fn: function() { - return _(this.model.values.sample(15)) - .map(function(x) { - return x.value; - }) - .value(); - } - } - }, - events: { - 'mousedown [data-hook=refresh]': 'refresh', - mouseup: 'stopTimer', - 'click .bubble > code': 'bubbleClicked' - }, - render: function() { - this.renderWithTemplate(this); - }, - refresh: function(event) { - if (!this.timer) { - this.timer = setInterval(this.refresh.bind(this), 600); - } else { - clearInterval(this.timer); - this.timer = setInterval(this.refresh.bind(this), 50); - } - if (event) { - event.preventDefault(); - } - this.render(); - // re-apply selections after refresh - this.parent.updateUI_distinct(); - }, - stopTimer: function() { - clearInterval(this.timer); - this.timer = null; - }, - bubbleClicked: function(evt) { - evt.stopPropagation(); - evt.preventDefault(); - - var value = evt.target.innerText; - if (this.model.getType() === 'Number') { - value = parseFloat(value, 10); - } - var chartEvt = { - d: { - label: evt.target.innerText, - value: value, - count: 1 - }, - self: evt.target, - evt: evt, - type: 'click', - source: 'unique' - }; - this.trigger('querybuilder', chartEvt); - } -}); diff --git a/src/app/minicharts/viz.js b/src/app/minicharts/viz.js deleted file mode 100644 index b59386bb4dd..00000000000 --- a/src/app/minicharts/viz.js +++ /dev/null @@ -1,155 +0,0 @@ -/* eslint indent:0, complexity:0 */ -var AmpersandView = require('ampersand-view'); -var _ = require('lodash'); -var $ = require('jquery'); -var d3 = require('d3'); -// var debug = require('debug')('mongodb-compass:minicharts:viz'); - -var svgTemplate = require('./svg-template.jade'); - -var VizView = AmpersandView.extend({ - _values: {}, - _autoWidth: false, - _autoHeight: false, - props: { - className: 'any', - vizFn: 'any', - chart: 'any', - debounceRender: { - type: 'boolean', - default: true - }, - renderMode: { - type: 'string', - values: ['canvas', 'svg', 'html'], - default: 'svg' - }, - width: { - type: 'any', - default: 'auto' - }, - height: { - type: 'any', - default: 400 - } - }, - bindings: { - width: [ - // set width attribute for svg, canvas - { - type: 'attribute', - name: 'width', - hook: 'viz-container' - }, - // set width style for html - { - type: function(el, value) { - $(el).width(value); - }, - hook: 'viz-container' - } - ], - height: [ - // set height attribute for svg, canvas - { - type: 'attribute', - name: 'height', - hook: 'viz-container' - }, - // set height style for html - { - type: function(el, value) { - $(el).height(value); - }, - hook: 'viz-container' - } - ], - className: { - type: 'attribute', - name: 'class', - hook: 'viz-container' - } - }, - initialize: function() { - if (this.width === 'auto' || this.width === undefined) { - this._autoWidth = true; - this.width = 0; - } - if (this.height === 'auto' || this.height === undefined) { - this._autoHeight = true; - this.height = 0; - } - - if (this._autoWidth || this._autoHeight) { - if (this.debounceRender) { - window.addEventListener('resize', _.debounce(this.redraw.bind(this), 100)); - } else { - window.addEventListener('resize', this.redraw.bind(this)); - } - } - - // pick html, canvas or svg template - if (!this.template) { - switch (this.renderMode) { - case 'canvas': - this.template = ''; - break; - case 'svg': - this.template = svgTemplate; - break; - case 'html': - default: - this.template = '
'; - break; - } - } - }, - - _measure: function() { - if (this.el) { - if (this._autoWidth) { - this.width = $(this.el).parent().width(); - } - if (this._autoHeight) { - this.height = $(this.el).parent().height(); - } - } - }, - - remove: function() { - // @todo, _onResize not defined, is this correct? - window.removeEventListener('resize', this._onResize); - return AmpersandView.prototype.remove.call(this); - }, - - render: function() { - this.renderWithTemplate(this); - - // measure only if width or height is missing - this._measure(); - - // call viz function - if (this.vizFn) { - this.chart = this.vizFn() - .options({ - model: this.model, - view: this, - el: this.el - }); - this.redraw(); - } - return this; - }, - - redraw: function() { - this.chart - .width(this.width) - .height(this.height); - - d3.select(this.el) - .datum(this.model.values.toJSON()) - .call(this.chart); - } -}); - -module.exports = VizView; diff --git a/src/app/models/sampled-schema.js b/src/app/models/sampled-schema.js deleted file mode 100644 index 974b0395dfd..00000000000 --- a/src/app/models/sampled-schema.js +++ /dev/null @@ -1,252 +0,0 @@ -var _ = require('lodash'); -var Schema = require('mongodb-schema').Schema; -var wrapError = require('./wrap-error'); -var FieldCollection = require('mongodb-schema').FieldCollection; -var SchemaStatusSubview = require('../statusbar/schema-subview'); -var filterableMixin = require('ampersand-collection-filterable'); -var debug = require('debug')('mongodb-compass:models:schema'); -var metrics = require('mongodb-js-metrics')(); -var app = require('ampersand-app'); - -/** - * wrapping mongodb-schema's FieldCollection with a filterable mixin - */ -var FilterableFieldCollection = FieldCollection.extend(filterableMixin, { - modelType: 'FilterableFieldCollection' -}); - -module.exports = Schema.extend({ - derived: { - sample_size: { - deps: ['count'], - fn: function() { - return this.count; - } - } - }, - session: { - // total number of documents counted under the given query - samplingStream: 'object', - analyzingStream: 'object', - total: 'number', - is_fetching: { - type: 'boolean', - default: false - }, - lastOptions: 'object' - }, - namespace: 'SampledSchema', - collections: { - fields: FilterableFieldCollection - }, - initialize: function() { - this.stopAnalyzing = this.stopAnalyzing.bind(this); - }, - /** - * Clear any data accumulated from sampling. - */ - reset: function() { - debug('resetting'); - this.count = 0; - this.fields.reset(); - }, - /** - * After you fetch an initial sample, next you'll want to drill-down to a - * smaller slice or drill back up to look at a larger slice. - * - * @example - * schema.fetch({}); - * schema.refine({a: 1}); - * schema.refine({a: 1, b: 1}); - * schema.refine({a: 2}); - * @param {Object} options - Passthrough options. - */ - refine: function(options) { - debug('refining %j', options); - this.reset(); - this.fetch(options); - }, - /** - * Take another sample on top of what you currently have. - * - * @example - * schema.fetch({size: 100}); - * // schema.documents.length is now 100 - * schema.more({size: 100}); - * // schema.documents.length is now 200 - * schema.more({size: 10}); - * // schema.documents.length is now 210 - * @param {Object} options - Passthrough options. - */ - more: function(options) { - debug('fetching more %j', options); - this.fetch(options); - }, - /** - * Get a sample of documents for a collection from the server. - * Really this should only be called directly from the `initialize` function - * - * @param {Object} [options] - See below. - * @option {Number} [size=100] Number of documents the sample should contain. - * @option {Object} [query={}] - * @option {Object} [fields=null] - */ - fetch: function(options) { - this.is_fetching = true; - options = _.defaults(options || this.lastOptions || {}, { - size: 1000, - query: {}, - fields: null - }); - this.lastOptions = _.clone(options); - - var model = this; - wrapError(this, options); - - var success = options.success; - options.success = function(resp) { - if (success) { - success(model, resp, options); - } - }; - - var start = new Date(); - var timeAtFirstDoc; - var erroredOnDocs = []; - var sampleCount = 0; - - var schemaStatusSubview = new SchemaStatusSubview({schema: this}); - - - var onFail = function(err) { - // hide statusbar animation, progressbar (top) - app.statusbar.animation = false; - app.statusbar.trickle(false); - app.statusbar.progressbar = false; - - // show error box with buttons for current stage (sampling/analyzing) - schemaStatusSubview.error = true; - schemaStatusSubview.showButtons(); - - // track error - metrics.error(err); - - // call error callback - process.nextTick(options.error.bind(null, err)); - - // trigger error so schema view can dismiss status bar - model.trigger('error'); - }; - - - var onEnd = function(err) { - model.is_fetching = false; - - if (err) { - return onFail(err); - } - app.statusbar.hide(true); - - // @note (imlucas): Any other metrics? Feedback on `Schema *`? - var totalTime = new Date() - start; - var timeToFirstDoc = timeAtFirstDoc - start; - - metrics.track('Schema', 'sampled', { - duration: totalTime, - 'query clauses count': _.keys(options.query).length, - 'total document count': model.total, - 'schema width': model.width, - 'schema depth': model.depth, - 'sample size': sampleCount, - 'errored document count': erroredOnDocs.length, - 'total sample time': timeToFirstDoc, - 'total analysis time': totalTime - timeToFirstDoc, - 'average analysis time per doc': (totalTime - timeToFirstDoc) / sampleCount - }); - model.trigger('sync'); - options.success({}); - }; - - model.trigger('request', {}, {}, options); - - app.statusbar.set({ - animation: true, - progressbar: true, - width: 100 - }); - app.statusbar.showSubview(schemaStatusSubview); - app.statusbar.width = 1; - app.statusbar.trickle(true); - - app.dataService.count(model.ns, options.query, options, function(err, count) { - if (err) { - return onFail(err); - } - model.total = count; - if (model.total === 0) { - model.is_fetching = false; - return onEnd(); - } - - var status = 0; - var numSamples = Math.min(options.size, count); - var stepSize = Math.ceil(Math.max(1, numSamples / 10)); - - model.samplingStream = app.dataService.sample(model.ns, options); - // pass in true for native (fast) sampler, false for ampersand-sampler - model.analyzingStream = model.stream(true); - - model.samplingStream - .on('error', function(sampleErr) { - model.samplingStream.destroy(); - model.analyzingStream.destroy(); - onFail(sampleErr); - }) - .pipe(model.analyzingStream) - .once('progress', function() { - timeAtFirstDoc = new Date(); - status = app.statusbar.width; - schemaStatusSubview.activeStep = 'analyzing'; - app.statusbar.trickle(false); - }) - .on('progress', function() { - sampleCount++; - if (sampleCount % stepSize === 0) { - var inc = (100 - status) * stepSize / numSamples; - app.statusbar.width += inc; - } - }) - .on('error', function(analysisErr) { - schemaStatusSubview.error = true; - onFail(analysisErr); - }) - .on('end', function() { - if (sampleCount === numSamples) { - return onEnd(); - } - }); - }); - }, - reSampleWithLongerTimeout: function() { - app.queryOptions.maxTimeMS = 60000; - this.fetch(); - }, - stopAnalyzing: function() { - if (this.is_fetching) { - this.is_fetching = false; - this.samplingStream.destroy(); - this.analyzingStream.destroy(); - } - app.statusbar.hide(true); - this.trigger('sync'); - }, - serialize: function() { - var res = this.getAttributes({ - props: true, - derived: true - }, true); - res = _.omit(res, ['name', 'sample_size']); - res.fields = this.fields.serialize(); - return res; - } -}); diff --git a/src/app/refine-view/index.jade b/src/app/refine-view/index.jade deleted file mode 100644 index ae8911205a5..00000000000 --- a/src/app/refine-view/index.jade +++ /dev/null @@ -1,6 +0,0 @@ -.refine-view-container - .query-input-container: .row: .col-md-12: form: .input-group(data-hook='refine-input-group') - input#refine_input.form-control.input-sm(type='text', data-hook='refine-input') - span.input-group-btn - button#apply_button.btn.btn-default.btn-sm(type='button', data-hook='apply-btn') Apply - button#reset_button.btn.btn-default.btn-sm(type='button', data-hook='reset-button') Reset diff --git a/src/app/refine-view/index.js b/src/app/refine-view/index.js deleted file mode 100644 index 76de4fc4a69..00000000000 --- a/src/app/refine-view/index.js +++ /dev/null @@ -1,194 +0,0 @@ -var $ = require('jquery'); -var _ = require('lodash'); -var AmpersandView = require('ampersand-view'); -var EditableQuery = require('../models/editable-query'); -var EJSON = require('mongodb-extended-json'); -var QueryStore = require('../../internal-packages/schema/lib/store'); -var Query = require('mongodb-language-model').Query; -var QueryOptions = require('../models/query-options'); - -// var metrics = require('mongodb-js-metrics')(); -var debug = require('debug')('scout:refine-view:index'); - -var indexTemplate = require('./index.jade'); - -var DEFAULT_QUERY = JSON.stringify(QueryOptions.DEFAULT_QUERY); - -module.exports = AmpersandView.extend({ - template: indexTemplate, - props: { - visible: { - type: 'boolean', - default: true - } - }, - session: { - queryOptions: 'state', - volatileQueryOptions: 'state', - volatileQuery: 'object' - }, - derived: { - notEmpty: { - deps: ['editableQuery.rawString'], - fn: function() { - return this.editableQuery.rawString !== DEFAULT_QUERY; - } - }, - hasChanges: { - deps: ['editableQuery.cleanString', 'queryOptions.queryString'], - fn: function() { - return this.editableQuery.cleanString !== this.queryOptions.queryString; - } - }, - applyEnabled: { - deps: ['editableQuery.valid', 'hasChanges'], - fn: function() { - return this.editableQuery.valid && this.hasChanges; - } - } - }, - children: { - editableQuery: EditableQuery - }, - bindings: { - visible: { - type: 'booleanClass', - no: 'hidden' - }, - 'editableQuery.rawString': [ - { - type: 'value', - hook: 'refine-input' - }, - { - hook: 'apply-btn', - type: function(el) { - this.highlightApplyBtnIfQueryNotApplied(el); - } - } - ], - // red input border while query is invalid - 'editableQuery.valid': { - type: 'booleanClass', - hook: 'refine-input-group', - yes: '', - no: 'has-error' - }, - notEmpty: { - type: 'toggle', - hook: 'reset-button' - }, - applyEnabled: { - type: 'booleanAttribute', - no: 'disabled', - hook: 'apply-btn' - } - }, - events: { - 'click [data-hook=apply-btn]': 'applyClicked', - 'click [data-hook=reset-button]': 'resetClicked', - 'input [data-hook=refine-input]': 'inputChanged', - 'submit form': 'submit' - }, - initialize: function() { - // this.volatileQuery = this.volatileQueryOptions.query; - // this.listenToAndRun(this.volatileQueryOptions, 'change:query', this.updateQueryListener); - QueryStore.listen(this.onQueryBufferChanged.bind(this)); - }, - updateQueryListener: function() { - // this.stopListening(this.volatileQuery, 'change:buffer', this.onQueryBufferChanged); - // this.volatileQuery = this.volatileQueryOptions.query; - // this.listenTo(this.volatileQuery, 'change:buffer', this.onQueryBufferChanged); - // this.editableQuery.rawString = this.volatileQueryOptions.queryString; - }, - onQueryBufferChanged: function(store) { - debug('store', store); - this.editableQuery.rawString = EJSON.stringify(store.query); - // this.editableQuery.rawString = EJSON.stringify(this.volatileQuery.serialize()); - }, - /** - * when user changes the text in the input field, copy the value into editableQuery. If the - * resulting query is valid, copy the query to volatileQueryOptions, so that the UI can update - * itself. - */ - inputChanged: function() { - var view = this; - view.editableQuery.rawString = view.queryByHook('refine-input').value; - if (view.editableQuery.valid) { - view.volatileQueryOptions.query = view.editableQuery.queryObject; - } - // re-focus the input field after the minicharts have updated - _.defer(function() { - $(view.queryByHook('refine-input')).focus(); - }); - }, - /** - * When the user hits reset, restore the original query options and update the refine bar to show - * the original query string (default is `{}`). - */ - resetClicked: function() { - if (this.queryOptions.queryString !== DEFAULT_QUERY) { - this.queryOptions.reset(); - this.volatileQueryOptions.reset(); - this.trigger('submit', this); - } else { - // currently still showing the view of default query `{}`, no need to resample - this.editableQuery.rawString = DEFAULT_QUERY; - this.volatileQueryOptions.query = this.editableQuery.queryObject; - } - }, - /** - * When the user hits refine, copy the query created from editableQuery to queryOptions (and - * clone to volatile as well, so they are in sync). This will also trigger a resample in - * CollectionView. - * - * Then copy the resulting string so that we show the correctly formatted query (with quotes). - */ - applyClicked: function() { - // The UI should not allow hitting refine on invalid queries, but just to be sure we - // deny it here, too. - if (!this.editableQuery.valid) { - return; - } - this.volatileQueryOptions.query = this.editableQuery.queryObject; - // clone the query - this.queryOptions.query = new Query(this.volatileQueryOptions.query.serialize(), { - parse: true - }); - // update the refine bar with a valid query string - this.editableQuery.rawString = this.queryOptions.queryString; - this.trigger('submit', this); - - this.unhighlightBtn($('[data-hook=\'apply-btn\']')[0]); - }, - /** - * Handler for hitting enter inside the input field. First defocus, then just delegate to - * applyClicked. - * @param {Object} evt the submit event. - */ - submit: function(evt) { - evt.preventDefault(); - // lose focus on input field first, see http://ampersandjs.com/docs#ampersand-dom-bindings-value - $(evt.delegateTarget).find('input').blur(); - if (this.editableQuery.valid) { - this.applyClicked(); - } - }, - highlightApplyBtnIfQueryNotApplied: function(applyBtn) { - if (!_.isUndefined(applyBtn)) { - if (this.editableQuery.valid && this.editableQuery.rawString !== this.queryOptions.queryString) { - this.highlightBtn(applyBtn); - } else { - this.unhighlightBtn(applyBtn); - } - } - }, - highlightBtn: function(btn) { - btn.classList.remove('btn-default'); - btn.classList.add('btn-info'); - }, - unhighlightBtn: function(btn) { - btn.classList.remove('btn-info'); - btn.classList.add('btn-default'); - } -}); diff --git a/src/app/refine-view/index.less b/src/app/refine-view/index.less deleted file mode 100644 index 07a945b6375..00000000000 --- a/src/app/refine-view/index.less +++ /dev/null @@ -1,27 +0,0 @@ -.refine-view-container { - position: relative; - z-index: 1; - - .query-input-container { - padding: 12px 10px 12px; - background: @gray8; - border-bottom: 1px solid @gray7; - - input[type='text'] { - font-family: @font-family-monospace; - background: @pw; - height: 28px; - & + .input-group-btn { - padding-left: 10px; - - .btn { - border-radius: 3px; - } - - &:last-child > .btn { - margin-left: 2px; - } - } - } - } -} diff --git a/src/app/sampling-message/index.jade b/src/app/sampling-message/index.jade deleted file mode 100644 index 7197348f473..00000000000 --- a/src/app/sampling-message/index.jade +++ /dev/null @@ -1,14 +0,0 @@ -.sampling-message - if is_sample - | Query returned  - b #{formatted_total_count} - |  #{total_count_document}. - | This report is based on a sample of  - b #{formatted_sample_size} - |  #{sample_size_document} - | (#{percentage}). - else - | Query returned  - b #{formatted_sample_size} - |  #{sample_size_document}. - i.help(data-hook='schema-sampling-results') diff --git a/src/app/sampling-message/index.js b/src/app/sampling-message/index.js deleted file mode 100644 index 770dcb3e9e5..00000000000 --- a/src/app/sampling-message/index.js +++ /dev/null @@ -1,96 +0,0 @@ -var View = require('ampersand-view'); -var pluralize = require('pluralize'); -var numeral = require('numeral'); - -var indexTemplate = require('./index.jade'); - -// var debug = require('debug')('mongodb-compass:sampling-message'); - -var SamplingMessageView = View.extend({ - template: indexTemplate, - session: { - parent: 'state' - }, - props: { - sample_size: { - type: 'number', - default: 0 - }, - total_count: { - type: 'number', - default: 0 - } - }, - derived: { - visible: { - deps: ['sample_size'], - fn: function() { - return this.sample_size > 0 || !this.model; - } - }, - percentage: { - deps: ['sample_size', 'total_count'], - fn: function() { - if (this.total_count === 0) { - return '0%'; - } - return numeral(this.sample_size / this.total_count).format('0.00%'); - } - }, - is_sample: { - deps: ['sample_size', 'total_count'], - fn: function() { - return this.model && (this.sample_size < this.total_count); - } - }, - formatted_total_count: { - deps: ['total_count'], - fn: function() { - return numeral(this.total_count).format('0,0'); - } - }, - formatted_sample_size: { - deps: ['sample_size'], - fn: function() { - return numeral(this.sample_size).format('0,0'); - } - }, - total_count_document: { - deps: ['total_count'], - fn: function() { - return pluralize('document', this.total_count); - } - }, - sample_size_document: { - deps: ['sample_size'], - fn: function() { - return pluralize('document', this.sample_size); - } - } - }, - bindings: { - visible: { - type: 'booleanClass', - no: 'hidden' - } - }, - initialize: function() { - if (this.model) { - this.listenTo(this.model, 'request', this.hide.bind(this)); - this.listenTo(this.model, 'sync', this.show.bind(this)); - } - }, - hide: function() { - this.sample_size = 0; - this.total_count = 0; - }, - show: function() { - this.sample_size = this.model.sample_size; - this.total_count = this.model.total; - this.render(); - }, - render: function() { - this.renderWithTemplate(this); - } -}); -module.exports = SamplingMessageView; diff --git a/src/app/sampling-message/index.less b/src/app/sampling-message/index.less deleted file mode 100644 index 8c1ab0b5b17..00000000000 --- a/src/app/sampling-message/index.less +++ /dev/null @@ -1,11 +0,0 @@ -.sampling-message { - border-bottom: 1px solid @gray7; - font-size: 13px; - font-weight: 200; - padding: 5px 25px 5px 25px; - background: @gray8; - - .message-container { - background: @gray7; - } -} diff --git a/src/app/statusbar/index.jade b/src/app/statusbar/index.jade deleted file mode 100644 index 7053904d7a1..00000000000 --- a/src/app/statusbar/index.jade +++ /dev/null @@ -1,14 +0,0 @@ -#statusbar - .progress(data-hook='outer-bar') - .progress-bar.progress-bar-striped.active(data-hook='inner-bar') - .sidebar(data-hook='static-sidebar') - ul.message-background.with-sidebar.centered(data-hook='message-container') - li - p(data-hook='message') - .spinner(data-hook='animation') - .rect1 - .rect2 - .rect3 - .rect4 - .rect5 - div(data-hook='subview-container') diff --git a/src/app/statusbar/index.js b/src/app/statusbar/index.js deleted file mode 100644 index 2f90b91cae3..00000000000 --- a/src/app/statusbar/index.js +++ /dev/null @@ -1,188 +0,0 @@ -var View = require('ampersand-view'); -var _ = require('lodash'); - -var debug = require('debug')('mongodb-compass:statusbar:index'); - -var indexTemplate = require('./index.jade'); - -var StatusbarView = View.extend({ - props: { - subview: { - type: 'object', - default: null - }, - width: { - type: 'number', - default: 0 - }, - message: { - type: 'string' - }, - staticSidebar: { - type: 'boolean', - default: false - }, - animation: { - type: 'boolean', - default: false - }, - visible: { - type: 'boolean', - default: false - }, - progressbar: { - type: 'boolean', - default: true - } - }, - session: { - trickleTimer: 'any' - }, - derived: { - /** - * Outer-bar height. - */ - height: { - deps: ['width', 'progressbar'], - fn: function() { - if (this.progressbar) { - return this.width > 0 ? 4 : 0; - } - return 0; - } - } - }, - template: indexTemplate, - bindings: { - staticSidebar: { - type: 'toggle', - hook: 'static-sidebar' - }, - animation: { - type: 'toggle', - hook: 'animation', - mode: 'visibility' - }, - message: [ - { - hook: 'message' - }, - { - type: 'toggle', - hook: 'message', - mode: 'visibility' - } - ], - height: { - hook: 'outer-bar', - type: function(el, value) { - el.style.height = value + 'px'; - } - }, - width: [ - { - hook: 'inner-bar', - type: function(el, value) { - el.style.width = value + '%'; - } - } - ], - progressbar: { - type: 'toggle', - hook: 'outer-bar' - }, - visible: { - type: 'booleanClass', - no: 'hidden' - } - }, - watch: function(view, collection) { - view.listenTo(collection, 'sync', this.onComplete.bind(this)); - view.listenTo(collection, 'request', this.onRequest.bind(this)); - return this; - }, - unwatch: function(view, collection) { - view.stopListening(collection, 'sync', this.onComplete.bind(this)); - view.stopListening(collection, 'request', this.onRequest.bind(this)); - return this; - }, - onRequest: function(model, resp, options) { - options = options || {}; - this.show(options.message); - }, - onComplete: function() { - this.hide(); - }, - fatal: function(err) { - this.visible = true; - this.animation = false; - this.message = 'Fatal Error: ' + err.message; - this.width = 0; - this.trickle(false); - clearInterval(this.trickleTimer); - }, - trickle: function(bool) { - if (bool) { - this.trickleTimer = setInterval(function() { - this.width = Math.min(98, this.width + 1); - }.bind(this), 600); - } else { - clearInterval(this.trickleTimer); - } - }, - show: function(options) { - options = _.defaults(options || {}, { - visible: true, - progressbar: true, - message: '', - width: 100, - animation: false - }); - debug('options are', options); - this.set(options); - }, - showMessage: function(message) { - if (!message) { - message = ''; - } else if (!_.isString(message)) { - /** - * @see https://jira.mongodb.org/browse/INT-1659 - */ - message = _.get(message, 'message', JSON.stringify(message)); - } - - this.visible = true; - this.message = message; - this.animation = false; - }, - showSubview: function(subview) { - if (this.subview) { - this.subview.remove(); - } - this.subview = subview; - subview.parent = this; - this.renderSubview(subview, this.queryByHook('subview-container')); - }, - hide: function(completed) { - this.message = ''; - this.animation = false; - this.staticSidebar = false; - if (this.subview) { - this.subview.remove(); - } - clearInterval(this.trickleTimer); - if (completed) { - this.width = 100; - var model = this; - _.delay(function() { - model.width = 0; - model.visible = false; - }, 500); - } else { - this.progressbar = false; - this.visible = false; - } - } -}); - -module.exports = StatusbarView; diff --git a/src/app/statusbar/schema-subview.jade b/src/app/statusbar/schema-subview.jade deleted file mode 100644 index eb5755ee541..00000000000 --- a/src/app/statusbar/schema-subview.jade +++ /dev/null @@ -1,31 +0,0 @@ -div#schema-status-subview - - div(data-hook='steps') - ul.steps - li#sampling-step - i(data-hook='sampling-indicator') - | Sampling Collection - li#analyzing-step - i(data-hook='analyzing-indicator') - | Analyzing Documents - - div.buttons(data-hook='buttons') - div#buttons-error - div.alert.alert-warning(role='alert', data-hook='sampling-info') - | Sampling took longer than  - span(data-hook='maxtimems') 10 seconds - |  on the database. As a safety measure, Compass aborts long-running queries.   - a.help(data-hook='schema-long-running-queries') - | Learn More - i.fa.fa-fw.fa-info-circle - br - div.btn.btn-info(data-hook='increase-maxtimems-button') Try for 1 minute - div.btn.btn-info(data-hook='next-action-button') - - div#buttons-waiting - div.alert.alert-info(role='alert', data-hook='analyzing-info') Document analysis is taking longer than expected.   - a.help(data-hook='schema-long-running-queries') - | Learn More - i.fa.fa-fw.fa-info-circle - br - div.btn.btn-info(data-hook='stop-analyzing-button') Stop and show partial results diff --git a/src/app/statusbar/schema-subview.js b/src/app/statusbar/schema-subview.js deleted file mode 100644 index 6e02e978062..00000000000 --- a/src/app/statusbar/schema-subview.js +++ /dev/null @@ -1,179 +0,0 @@ -var View = require('ampersand-view'); -var ms = require('ms'); -var app = require('ampersand-app'); - -// var debug = require('debug')('mongodb-compass:statusbar:schema-subview'); -var subviewTemplate = require('./schema-subview.jade'); - -var SHOW_STEPS_MS = 3000; - -module.exports = View.extend({ - template: subviewTemplate, - props: { - schema: { - type: 'state', - default: null - }, - timer: { - type: 'any', - default: null - }, - activeStep: { - type: 'string', - values: ['sampling', 'analyzing'], - default: 'sampling' - }, - error: { - type: 'boolean', - default: false - }, - stepsVisible: { - type: 'boolean', - default: false - }, - buttonsVisible: { - type: 'boolean', - default: false - } - }, - derived: { - nextActionLabel: { - deps: ['activeStep'], - fn: function() { - return this.activeStep === 'sampling' ? - 'Create new Query' : 'Show partial results'; - } - }, - samplingState: { - deps: ['activeStep', 'error'], - fn: function() { - if (this.activeStep === 'sampling') { - return this.error ? 'error' : 'active'; - } - return 'complete'; - } - }, - analyzingState: { - deps: ['activeStep', 'error'], - fn: function() { - if (this.activeStep === 'analyzing') { - return this.error ? 'error' : 'active'; - } - return 'waiting'; - } - }, - maxTimeMSStr: { - deps: ['app.queryOptions.maxTimeMS'], - fn: function() { - return ms(app.queryOptions.maxTimeMS, {long: true}); - } - } - }, - bindings: { - nextActionLabel: { - hook: 'next-action-button', - type: 'text' - }, - stepsVisible: { - type: 'toggle', - hook: 'steps', - mode: 'visibility' - }, - buttonsVisible: { - type: 'toggle', - hook: 'buttons', - mode: 'visibility' - }, - error: { - type: 'toggle', - no: '#buttons-waiting', - yes: '#buttons-error' - }, - activeStep: [ - // { - // type: 'switch', - // hook: 'buttons', - // cases: { - // sampling: '#buttons-sampling', - // analyzing: '#buttons-analyzing' - // } - // }, - { - type: 'switchClass', - name: 'is-active', - cases: { - sampling: '#sampling-step', - analyzing: '#analyzing-step' - } - } - ], - maxTimeMSStr: { - hook: 'maxtimems' - }, - samplingState: { - hook: 'sampling-indicator', - type: function(el, value) { - switch (value) { - case 'active': el.className = 'fa fa-fw fa-spin fa-circle-o-notch'; break; - case 'complete': el.className = 'mms-icon-check'; break; - case 'error': el.className = 'fa fa-fw fa-warning'; break; - default: el.className = 'fa fa-fw'; - } - } - }, - analyzingState: { - hook: 'analyzing-indicator', - type: function(el, value) { - switch (value) { - case 'active': el.className = 'fa fa-fw fa-spin fa-circle-o-notch'; break; - case 'complete': el.className = 'mms-icon-check'; break; - case 'error': el.className = 'fa fa-fw fa-warning'; break; - default: el.className = 'fa fa-fw'; - } - } - } - }, - events: { - 'click [data-hook=stop-analyzing-button]': 'stopAnalyzingClicked', - 'click [data-hook=partial-results-button]': 'stopAnalyzingClicked', - 'click [data-hook=next-action-button]': 'nextActionClicked', - 'click [data-hook=increase-maxtimems-button]': 'resampleWithLongerTimoutClicked' - }, - render: function() { - this.renderWithTemplate(this); - this.on('change:activeStep', this.analyzingBegun.bind(this)); - this.timer = setTimeout(this.showSteps.bind(this), SHOW_STEPS_MS); - }, - analyzingBegun: function() { - var SHOW_ANALYZING_BUTTONS_MS = app.queryOptions.maxTimeMS + 1000; - setTimeout(this.showButtons.bind(this), SHOW_ANALYZING_BUTTONS_MS); - }, - showSteps: function() { - clearTimeout(this.timer); - this.stepsVisible = true; - }, - showButtons: function() { - this.stepsVisible = true; - this.buttonsVisible = true; - }, - stopAnalyzingClicked: function() { - if (this.schema) { - this.schema.stopAnalyzing(); - } - }, - resampleWithLongerTimoutClicked: function() { - this.schema.reSampleWithLongerTimeout(); - }, - nextActionClicked: function() { - if (this.schema && this.schema.count > 0) { - return this.schema.stopAnalyzing(); - } - app.statusbar.hide(); - }, - remove: function() { - clearTimeout(this.timer); - this.stepsVisible = false; - this.buttonsVisible = false; - View.prototype.remove.call(this); - } -}); From 6cd10a95f0975402a81f13c625ce3a20a5b7c963 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 14:40:08 -0400 Subject: [PATCH 06/15] :bug: Fix flexbox alignItems warning ``` /Users/lucas/compass/node_modules/fbjs/lib/warning.js:36 Warning: Failed prop type: Invalid prop `alignItems` of value `start` supplied to `FlexBox`, expected one of ["flex-start","flex-end","center","stretch","baseline"]. ``` --- .../explain/lib/components/explain-summary.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal-packages/explain/lib/components/explain-summary.jsx b/src/internal-packages/explain/lib/components/explain-summary.jsx index 49bbe6e4186..e112fe1c1fb 100644 --- a/src/internal-packages/explain/lib/components/explain-summary.jsx +++ b/src/internal-packages/explain/lib/components/explain-summary.jsx @@ -19,7 +19,7 @@ class ExplainSummary extends React.Component { return (

Query Performance Summary

- +
Date: Thu, 20 Oct 2016 14:40:45 -0400 Subject: [PATCH 07/15] sampling-message removed earlier --- src/app/index.less | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/index.less b/src/app/index.less index cec2594c9ff..6c189e21fc6 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -7,7 +7,6 @@ @import "collection-stats/index.less"; @import "connect/index.less"; @import "home/index.less"; -@import "sampling-message/index.less"; @import "tour/index.less"; @import "sidebar/index.less"; @import "network-optin/index.less"; From dd2918e96982b3e6d7b24dd00db881367f493bf5 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 14:45:41 -0400 Subject: [PATCH 08/15] Recover sampling-message styles --- src/internal-packages/query/styles/index.less | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/internal-packages/query/styles/index.less b/src/internal-packages/query/styles/index.less index 1a47bbb9be6..67bf05819fc 100644 --- a/src/internal-packages/query/styles/index.less +++ b/src/internal-packages/query/styles/index.less @@ -29,3 +29,15 @@ } } } + +.sampling-message { + border-bottom: 1px solid @gray7; + font-size: 13px; + font-weight: 200; + padding: 5px 25px 5px 25px; + background: @gray8; + + .message-container { + background: @gray7; + } +} From ab378630075848c902ea2180683a8d6edf26b868 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 15:00:54 -0400 Subject: [PATCH 09/15] crud styles have index.less like everything else --- src/internal-packages/crud/styles/{crud.less => index.less} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/internal-packages/crud/styles/{crud.less => index.less} (100%) diff --git a/src/internal-packages/crud/styles/crud.less b/src/internal-packages/crud/styles/index.less similarity index 100% rename from src/internal-packages/crud/styles/crud.less rename to src/internal-packages/crud/styles/index.less From 834db94e55941e10d8e09d4df8da6414a66c7d4c Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 15:06:17 -0400 Subject: [PATCH 10/15] Fix missing return in error callback --- src/app/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/index.js b/src/app/index.js index 6f413cebfb4..ccba9e48a3f 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -218,7 +218,7 @@ var Application = View.extend({ debug('preferences fetched, now getting user'); User.getOrCreate(this.preferences.currentUserId, function(err, user) { if (err) { - done(err); + return done(err); } this.user.set(user.serialize()); this.user.trigger('sync'); From ae07d83f6174f7f8dcb82a82d17d1fb387d32258 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 15:07:50 -0400 Subject: [PATCH 11/15] Remove unused statusbar code --- src/app/index.js | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/app/index.js b/src/app/index.js index ccba9e48a3f..c6ca663177d 100644 --- a/src/app/index.js +++ b/src/app/index.js @@ -52,10 +52,10 @@ var QueryOptions = require('./models/query-options'); var Connection = require('./models/connection'); var MongoDBInstance = require('./models/mongodb-instance'); var Preferences = require('./models/preferences'); -var ApplicationStore = require('hadron-reflux-store').ApplicationStore; var User = require('./models/user'); + +var ApplicationStore = require('hadron-reflux-store').ApplicationStore; var Router = require('./router'); -// var Statusbar = require('./statusbar'); var migrateApp = require('./migrations'); var metricsSetup = require('./metrics'); var metrics = require('mongodb-js-metrics')(); @@ -162,10 +162,6 @@ var Application = View.extend({ * @see notifications.js */ notifications: 'state', - /** - * @see statusbar.js - */ - statusbar: 'state', /** * Details of the MongoDB Instance we're currently connected to. */ @@ -328,12 +324,6 @@ var Application = View.extend({ } }); debug('rendering statusbar...'); - - // this.statusbar = new Statusbar({ - // el: this.queryByHook('statusbar') - // }); - // this.statusbar.render(); - this.statusComponent = app.appRegistry.getComponent('Status.ProgressBar'); ReactDOM.render(React.createElement(this.statusComponent), this.queryByHook('statusbar')); @@ -450,12 +440,6 @@ app.extend({ } }); -// Object.defineProperty(app, 'statusbar', { -// get: function() { -// return state.statusbar; -// } -// }); - Object.defineProperty(app, 'autoUpdate', { get: function() { return state.autoUpdate; @@ -510,11 +494,6 @@ Object.defineProperty(app, 'state', { } }); -app.init(); - -// expose app globally for debugging purposes -window.app = app; - // add Reflux store method to listen to external stores const Reflux = require('reflux'); const packageActivationCompleted = require('hadron-package-manager/lib/action').packageActivationCompleted; @@ -526,4 +505,9 @@ Reflux.StoreMethods.listenToExternalStore = function(storeKey, callback) { }); }; +app.init(); + +// expose app globally for debugging purposes +window.app = app; + console.timeEnd('app/index.js'); From a5601a0f919aa7413aa80a3e1707984322ae794b Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 15:08:58 -0400 Subject: [PATCH 12/15] crud styles moved --- src/app/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/index.less b/src/app/index.less index 6c189e21fc6..dda5c17b523 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -18,7 +18,7 @@ // Packages // @todo don't hard-code these, style manager needs to handle package styles -@import "../internal-packages/crud/styles/crud.less"; +@import "../internal-packages/crud/styles/index.less"; @import "../internal-packages/status/styles/index.less"; @import "../internal-packages/query/styles/index.less"; @import "../internal-packages/schema/styles/index.less"; From ddf5d1f2199aae598efbdf0b81fce7a431c716d3 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 16:45:32 -0400 Subject: [PATCH 13/15] :fire: Move collection-stats to only place it's used: home makes migration to a compass package easier --- src/app/{ => home}/collection-stats/index.jade | 0 src/app/{ => home}/collection-stats/index.js | 0 src/app/{ => home}/collection-stats/index.less | 0 src/app/home/collection.js | 2 +- src/app/home/index.less | 2 ++ src/app/index.less | 1 - 6 files changed, 3 insertions(+), 2 deletions(-) rename src/app/{ => home}/collection-stats/index.jade (100%) rename src/app/{ => home}/collection-stats/index.js (100%) rename src/app/{ => home}/collection-stats/index.less (100%) diff --git a/src/app/collection-stats/index.jade b/src/app/home/collection-stats/index.jade similarity index 100% rename from src/app/collection-stats/index.jade rename to src/app/home/collection-stats/index.jade diff --git a/src/app/collection-stats/index.js b/src/app/home/collection-stats/index.js similarity index 100% rename from src/app/collection-stats/index.js rename to src/app/home/collection-stats/index.js diff --git a/src/app/collection-stats/index.less b/src/app/home/collection-stats/index.less similarity index 100% rename from src/app/collection-stats/index.less rename to src/app/home/collection-stats/index.less diff --git a/src/app/home/collection.js b/src/app/home/collection.js index df775d1f776..3ce0884e665 100644 --- a/src/app/home/collection.js +++ b/src/app/home/collection.js @@ -1,6 +1,6 @@ var View = require('ampersand-view'); var Action = require('hadron-action'); -var CollectionStatsView = require('../collection-stats'); +var CollectionStatsView = require('./collection-stats'); var MongoDBCollection = require('../models/mongodb-collection'); var React = require('react'); var ReactDOM = require('react-dom'); diff --git a/src/app/home/index.less b/src/app/home/index.less index a3b156a8d95..769499e90a4 100644 --- a/src/app/home/index.less +++ b/src/app/home/index.less @@ -1,3 +1,5 @@ +@import "./collection-stats/index.less" + .page { max-height: 100%; min-height: 100%; diff --git a/src/app/index.less b/src/app/index.less index dda5c17b523..d3a89100fe0 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -4,7 +4,6 @@ @import "./styles/caret.less"; // Components -@import "collection-stats/index.less"; @import "connect/index.less"; @import "home/index.less"; @import "tour/index.less"; From 0b9399bf7786a5c0339e276301ea00b46c05f823 Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 16:46:09 -0400 Subject: [PATCH 14/15] :fire: Metrics index.less has been empty for who knows how long... --- src/app/index.less | 1 - src/app/metrics/index.less | 0 2 files changed, 1 deletion(-) delete mode 100644 src/app/metrics/index.less diff --git a/src/app/index.less b/src/app/index.less index d3a89100fe0..8ca9e16a161 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -12,7 +12,6 @@ @import "identify/index.less"; @import "../auto-update/index.less"; -@import "./metrics/index.less"; @import "./styles/mapbox-gl.css"; // Packages diff --git a/src/app/metrics/index.less b/src/app/metrics/index.less deleted file mode 100644 index e69de29bb2d..00000000000 From d41ade567f3ad8ee50be4a7e6214bcdca79e72da Mon Sep 17 00:00:00 2001 From: Lucas Hrabovsky Date: Thu, 20 Oct 2016 16:46:45 -0400 Subject: [PATCH 15/15] Move mapbox styles to schema package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where they’re actually used. --- src/internal-packages/schema/styles/index.less | 2 ++ src/{app => internal-packages/schema}/styles/mapbox-gl.css | 0 2 files changed, 2 insertions(+) rename src/{app => internal-packages/schema}/styles/mapbox-gl.css (100%) diff --git a/src/internal-packages/schema/styles/index.less b/src/internal-packages/schema/styles/index.less index 74a114114d5..807995d5d1c 100644 --- a/src/internal-packages/schema/styles/index.less +++ b/src/internal-packages/schema/styles/index.less @@ -1,3 +1,5 @@ +@import "./styles/mapbox-gl.css"; + // minicharts @mc-blue0: #43B1E5; @mc-blue1: lighten(@mc-blue0, 7.5%); diff --git a/src/app/styles/mapbox-gl.css b/src/internal-packages/schema/styles/mapbox-gl.css similarity index 100% rename from src/app/styles/mapbox-gl.css rename to src/internal-packages/schema/styles/mapbox-gl.css