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>'].