You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/08/17 17:50:39 UTC

[couchdb-fauxton] branch master updated: add query history to Mango UI (#958)

This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new e1404a4  add query history to Mango UI (#958)
e1404a4 is described below

commit e1404a4d94e77d890177059677ac8e317654f0f2
Author: Will Holley <wi...@gmail.com>
AuthorDate: Thu Aug 17 18:50:37 2017 +0100

    add query history to Mango UI (#958)
    
    Adds a dropdown which shows the previous 5 Mango queries executed
    against the current database.
    
    Query history is persisted in localstorage (so only persists locally
    to the browesr) and is currently limited to 5 items.
    
    Where no history exists, we populate the history with the default
    query.
---
 app/addons/documents/assets/less/view-editor.less  |  19 ++++
 app/addons/documents/index-results/actions.js      |   8 ++
 .../documents/mango/__tests__/mango.store.test.js  | 123 +++++++++++++++++++++
 app/addons/documents/mango/mango.components.js     |  28 ++++-
 app/addons/documents/mango/mango.stores.js         |  74 ++++++++++---
 .../documents/mango/tests/mango.storesSpec.js      |  37 -------
 assets/less/variables.less                         |   4 +
 jest-setup.js                                      |   2 +
 package.json                                       |   1 +
 9 files changed, 244 insertions(+), 52 deletions(-)

diff --git a/app/addons/documents/assets/less/view-editor.less b/app/addons/documents/assets/less/view-editor.less
index d319b5e..52a712c 100644
--- a/app/addons/documents/assets/less/view-editor.less
+++ b/app/addons/documents/assets/less/view-editor.less
@@ -64,6 +64,25 @@
   }
 }
 
+.mango-history {
+  width: inherit;
+  overflow: visible;
+
+  div {
+    z-index: 100;
+  }
+
+  .mango-history-entry {
+    background-color: @queryHistoryBGColor;
+    color: @queryHistoryColor;
+
+    &.is-selected, &.is-focused {
+      background-color: @brandHighlight;
+      color: @queryHistoryBGColor;
+    }
+  }
+}
+
 // 940px grid without margin
 // -------------------------
 @gridColumnWidthNoMargin:         60px;
diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js
index b893313..c5e2a25 100644
--- a/app/addons/documents/index-results/actions.js
+++ b/app/addons/documents/index-results/actions.js
@@ -14,6 +14,7 @@ import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./actiontypes";
 import Stores from "./stores";
 import SidebarActions from "../sidebar/actions";
+import MangoActionTypes from "../mango/mango.actiontypes";
 var indexResultsStore = Stores.indexResultsStore;
 
 var errorMessage = function (ids) {
@@ -113,6 +114,13 @@ export default {
 
     this.clearResults();
 
+    FauxtonAPI.dispatch({
+      type: MangoActionTypes.MANGO_NEW_QUERY_FIND_CODE,
+      options: {
+        code: options.queryCode
+      }
+    });
+
     return collection
       .setQuery(query)
       .fetch()
diff --git a/app/addons/documents/mango/__tests__/mango.store.test.js b/app/addons/documents/mango/__tests__/mango.store.test.js
new file mode 100644
index 0000000..e3d8c00
--- /dev/null
+++ b/app/addons/documents/mango/__tests__/mango.store.test.js
@@ -0,0 +1,123 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import Stores from "../mango.stores";
+import testUtils from "../../../../../test/mocha/testUtils";
+var assert = testUtils.assert;
+var store;
+
+describe('Mango Store', () => {
+  describe('getQueryCode', () => {
+
+    beforeEach(() => {
+      window.localStorage.clear();
+      store = new Stores.MangoStore();
+    });
+
+    it('returns a default query', () => {
+      assert.ok(store.getQueryFindCode());
+    });
+
+    it('can store query in history', () => {
+      store.addQueryHistory({selector: 'foo'});
+      const history = store.getHistory();
+      assert.equal(history[0].label, '{"selector":"foo"}');
+      assert.equal(history[0].value, '{\n   "selector": "foo"\n}');
+    });
+
+    it('does not add duplicate history', () => {
+      store.addQueryHistory({selector: 'foo'});
+      store.addQueryHistory({selector: 'foo'});
+
+      const history = store.getHistory();
+
+      assert.equal(history[0].label, '{"selector":"foo"}');
+      assert.equal(history.length, 2);
+    });
+
+    it('does not add duplicate history for selector with different formatting', () => {
+      store.addQueryHistory({selector: 'foo'});
+      store.addQueryHistory('{"selector": "foo"}');
+      store.addQueryHistory('{"selector":"foo"\n}');
+
+      const history = store.getHistory();
+
+      assert.equal(history[0].label, '{"selector":"foo"}');
+      assert.equal(history.length, 2);
+    });
+
+     it('promotes existing history entry to top when used', () => {
+      store.addQueryHistory({selector: 'foo'});
+      store.addQueryHistory({selector: 'bar'});
+      var history = store.getHistory();
+      assert.equal(history[0].label, '{"selector":"bar"}');
+
+      store.addQueryHistory({selector: 'foo'});
+      history = store.getHistory();
+      assert.equal(history[0].label, '{"selector":"foo"}');
+      assert.equal(history.length, 3);
+    });
+
+    it('limits the number of history items to 5', () => {
+      store.addQueryHistory({selector: '1'});
+      store.addQueryHistory({selector: '2'});
+      store.addQueryHistory({selector: '3'});
+      store.addQueryHistory({selector: '4'});
+      store.addQueryHistory({selector: '5'});
+      store.addQueryHistory({selector: '6'});
+
+      const history = store.getHistory();
+      assert.equal(history.length, 5);
+    });
+
+    it('can store query in history with custom label', () => {
+      store.addQueryHistory({selector: 'foo'}, 'demo');
+      const history = store.getHistory();
+      assert.equal(history[0].label, 'demo');
+      assert.equal(history[0].value, '{\n   "selector": "foo"\n}');
+    });
+
+    it('history is persisted by key', () => {
+      store.setHistoryKey('test');
+      store.addQueryHistory({selector: 'foo'}, 'demo');
+      var history = store.getHistory();
+      assert.equal(history[0].label, 'demo');
+
+      const store2 = new Stores.MangoStore();
+      store2.setHistoryKey('test');
+      history = store2.getHistory();
+      assert.equal(history[0].label, 'demo');
+    });
+
+    it('different history for different keys', () => {
+      store.setHistoryKey('test');
+      store.addQueryHistory({selector: 'foo'}, 'demo');
+      var history = store.getHistory();
+      assert.equal(history[0].label, 'demo');
+      assert.equal(history.length, 2);
+
+      const store2 = new Stores.MangoStore();
+      store2.setHistoryKey('test2');
+      store2.addQueryHistory({selector: 'bar'}, 'demo2');
+      history = store2.getHistory();
+      assert.equal(history[0].label, 'demo2');
+      assert.equal(history.length, 2);
+    });
+
+    it('initializes default query code with most recent history', () => {
+      store.addQueryHistory({selector: 'foo'}, 'demo');
+      const history = store.getHistory();
+      const code = store.getQueryFindCode();
+      assert.equal(history[0].value, code);
+    });
+  });
+});
diff --git a/app/addons/documents/mango/mango.components.js b/app/addons/documents/mango/mango.components.js
index 285ef5a..290afb5 100644
--- a/app/addons/documents/mango/mango.components.js
+++ b/app/addons/documents/mango/mango.components.js
@@ -18,6 +18,7 @@ import Actions from "./mango.actions";
 import ReactComponents from "../../components/react-components";
 import IndexResultActions from "../index-results/actions";
 import "../../../../assets/js/plugins/prettify";
+import ReactSelect from "react-select";
 
 var mangoStore = Stores.mangoStore;
 var getDocUrl = app.helpers.getDocUrl;
@@ -34,7 +35,8 @@ var MangoQueryEditorController = React.createClass({
   getStoreState: function () {
     return {
       queryCode: mangoStore.getQueryFindCode(),
-      database: mangoStore.getDatabase()
+      database: mangoStore.getDatabase(),
+      history: mangoStore.getHistory()
     };
   },
 
@@ -78,7 +80,9 @@ var MangoQueryEditorController = React.createClass({
         docs={getDocUrl('MANGO_SEARCH')}
         exampleCode={this.state.queryCode}
         onExplainQuery={this.runExplain}
-        changedQuery={this.state.changedQuery} />
+        history={this.state.history}
+        onHistorySelected={this.historySelected}
+        />
     );
   },
 
@@ -119,6 +123,10 @@ var MangoQueryEditorController = React.createClass({
       database: this.state.database,
       queryCode: this.getMangoEditor().getEditorValue()
     });
+  },
+
+  historySelected: function(selectedItem) {
+    this.getMangoEditor().setEditorValue(selectedItem.value);
   }
 });
 
@@ -127,6 +135,18 @@ var MangoEditor = React.createClass({
     return (
       <div className="mango-editor-wrapper">
         <form className="form-horizontal" onSubmit={this.props.onSubmit}>
+          <div className="padded-box">
+            <ReactSelect
+                className="mango-history"
+                options={this.props.history}
+                ref="history"
+                placeholder="Query history"
+                searchable={false}
+                clearable={false}
+                autosize={false}
+                onChange={this.props.onHistorySelected}
+                />
+          </div>
           <PaddedBorderedBox>
             <CodeEditorPanel
               id="query-field"
@@ -147,6 +167,10 @@ var MangoEditor = React.createClass({
     );
   },
 
+  setEditorValue: function (value) {
+    return this.refs.field.getEditor().setValue(value);
+  },
+
   getEditorValue: function () {
     return this.refs.field.getValue();
   },
diff --git a/app/addons/documents/mango/mango.stores.js b/app/addons/documents/mango/mango.stores.js
index 7f19db8..399a6de 100644
--- a/app/addons/documents/mango/mango.stores.js
+++ b/app/addons/documents/mango/mango.stores.js
@@ -10,6 +10,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import app from "../../../app";
 import FauxtonAPI from "../../../core/api";
 import ActionTypes from "./mango.actiontypes";
 import IndexActionTypes from "../index-results/actiontypes";
@@ -29,12 +30,13 @@ var defaultQueryFindCode = {
 
 var Stores = {};
 
+const HISTORY_LIMIT = 5;
+
 Stores.MangoStore = FauxtonAPI.Store.extend({
 
   initialize: function () {
-    this._queryFindCode = defaultQueryFindCode;
+    this.setHistoryKey('default');
     this._queryIndexCode = defaultQueryIndexCode;
-    this._queryFindCodeChanged = false;
   },
 
   getQueryIndexCode: function () {
@@ -46,23 +48,16 @@ Stores.MangoStore = FauxtonAPI.Store.extend({
   },
 
   getQueryFindCode: function () {
-    return this.formatCode(this._queryFindCode);
-  },
-
-  setQueryFindCode: function (options) {
-    this._queryFindCode = options.code;
+    return this.getHistory()[0].value;
   },
 
   formatCode: function (code) {
-    return JSON.stringify(code, null, '  ');
-  },
-
-  getQueryFindCodeChanged: function () {
-    return this._queryFindCodeChanged;
+    return JSON.stringify(code, null, 3);
   },
 
   setDatabase: function (options) {
     this._database = options.database;
+    this.setHistoryKey(options.database.id);
   },
 
   getDatabase: function () {
@@ -77,6 +72,59 @@ Stores.MangoStore = FauxtonAPI.Store.extend({
     return this._explainPlan;
   },
 
+  getHistoryKey: function() {
+    return this._historyKey;
+  },
+
+  setHistoryKey: function(key) {
+    this._historyKey = key + '_queryhistory';
+  },
+
+  createHistoryEntry: function(queryObject) {
+    // ensure we're working with a deserialized query object
+    const object = typeof queryObject === "string" ? JSON.parse(queryObject) : queryObject;
+
+    const singleLineValue = JSON.stringify(object);
+    const multiLineValue = this.formatCode(object);
+
+    return {
+      label: singleLineValue,
+      value: multiLineValue,
+      className: 'mango-history-entry'
+    };
+  },
+
+  addQueryHistory: function (value, label) {
+    var history = this.getHistory();
+
+    const historyEntry = this.createHistoryEntry(value);
+    historyEntry.label = label || historyEntry.label;
+
+    // remove existing entry if it exists
+    var indexOfExisting = history.findIndex(i => i.value === historyEntry.value);
+    if (indexOfExisting > -1) {
+      history.splice(indexOfExisting, 1);
+    }
+
+    // insert item at head of array
+    history.splice(0, 0, historyEntry);
+
+    // limit array length
+    if (history.length > HISTORY_LIMIT) {
+      history.splice(HISTORY_LIMIT, 1);
+    }
+
+    app.utils.localStorageSet(this.getHistoryKey(), history);
+  },
+
+  getDefaultHistory: function () {
+    return [this.createHistoryEntry(defaultQueryFindCode)];
+  },
+
+  getHistory: function () {
+    return app.utils.localStorageGet(this.getHistoryKey()) || this.getDefaultHistory();
+  },
+
   dispatch: function (action) {
     switch (action.type) {
 
@@ -89,7 +137,7 @@ Stores.MangoStore = FauxtonAPI.Store.extend({
       break;
 
       case ActionTypes.MANGO_NEW_QUERY_FIND_CODE:
-        this.setQueryFindCode(action.options);
+        this.addQueryHistory(action.options.code);
       break;
 
       case ActionTypes.MANGO_EXPLAIN_RESULTS:
diff --git a/app/addons/documents/mango/tests/mango.storesSpec.js b/app/addons/documents/mango/tests/mango.storesSpec.js
deleted file mode 100644
index d1078f0..0000000
--- a/app/addons/documents/mango/tests/mango.storesSpec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../../core/api";
-import Stores from "../mango.stores";
-import testUtils from "../../../../../test/mocha/testUtils";
-var assert = testUtils.assert;
-var dispatchToken;
-var store;
-
-describe('Mango Store', function () {
-
-  describe('getQueryCode', function () {
-
-    beforeEach(function () {
-      store = new Stores.MangoStore();
-      dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch);
-    });
-
-    afterEach(function () {
-      FauxtonAPI.dispatcher.unregister(dispatchToken);
-    });
-
-    it('returns a default query', function () {
-      assert.ok(store.getQueryFindCode());
-    });
-  });
-});
diff --git a/assets/less/variables.less b/assets/less/variables.less
index 47d9b76..57aec02 100644
--- a/assets/less/variables.less
+++ b/assets/less/variables.less
@@ -116,6 +116,10 @@
 @successAlertColor: #448c11;
 @errorAlertColor: #c45b55;
 
+/* query history */
+@queryHistoryBGColor: white;
+@queryHistoryColor: @defaultText;
+
 /*
   -- logo image paths --
   These are set during webpack bundle through your settings.json file.
diff --git a/jest-setup.js b/jest-setup.js
index ec63056..204ff71 100644
--- a/jest-setup.js
+++ b/jest-setup.js
@@ -12,7 +12,9 @@
 
 require('jest');
 require('whatwg-fetch');
+require('mock-local-storage');
 
+window.localStorage = global.localStorage;
 window.$ = window.jQuery = require('jquery');
 window._ = require('lodash');
 window.Backbone = require('backbone');
diff --git a/package.json b/package.json
index 2804a83..0350369 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
     "mocha": "~3.1.2",
     "mocha-loader": "^1.1.0",
     "mocha-phantomjs": "git+https://github.com/garrensmith/mocha-phantomjs.git",
+    "mock-local-storage": "^1.0.4",
     "nightwatch": "~0.9.0",
     "phantomjs-prebuilt": "^2.1.7",
     "react-addons-test-utils": "~15.4.2",

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].