diff --git a/package.json b/package.json index 1fbc1d41055..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", @@ -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/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; - } -}); 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.js b/src/app/index.js index f68c4179aa1..c6ca663177d 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(); @@ -44,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')(); @@ -154,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. */ @@ -210,7 +214,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'); @@ -320,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')); @@ -442,12 +440,6 @@ app.extend({ } }); -// Object.defineProperty(app, 'statusbar', { -// get: function() { -// return state.statusbar; -// } -// }); - Object.defineProperty(app, 'autoUpdate', { get: function() { return state.autoUpdate; @@ -502,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; @@ -518,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'); diff --git a/src/app/index.less b/src/app/index.less index cec2594c9ff..8ca9e16a161 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -4,22 +4,19 @@ @import "./styles/caret.less"; // Components -@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"; @import "identify/index.less"; @import "../auto-update/index.less"; -@import "./metrics/index.less"; @import "./styles/mapbox-gl.css"; // 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"; diff --git a/src/app/metrics/index.less b/src/app/metrics/index.less deleted file mode 100644 index e69de29bb2d..00000000000 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/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', 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); - } -}); 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 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

- +