From a979ab72811902bf523b9072599dd09bcf1b0448 Mon Sep 17 00:00:00 2001 From: Kasper Nilsson Date: Thu, 11 Aug 2016 15:57:01 -0700 Subject: [PATCH] Implement simple autocomplete for the search bar This change modifies the existing search bar component by replacing the existing iron-input implementation with a gr-autocomplete component. Rudimentary autocompletion for the search bar now exists -- specific autocomplete features, I.E. backend querying for possible usernames when 'author:' is entered, are yet to be implemented. This task required modification of the gr-autocomplete element to support multiple search terms -- this was accomplished by the addition of a boolean prop, 'multi', that is false by default. When multi is true, the component will only look to autocomplete the substring from the index of the last space til the end. Possible enhancements to this change include: - addition of a 'delimiter' prop, as opposed to the hardcoded space delimiter - UI modifications to allow deleting of specific search terms, like a multi-select tag-styled input. Bug: Issue 4276 Change-Id: Ic04981af06ba34dd1d58b023fcf444e4168d6b18 --- .../core/gr-search-bar/gr-search-bar.html | 16 ++- .../core/gr-search-bar/gr-search-bar.js | 108 +++++++++++++++++- .../gr-search-bar/gr-search-bar_test.html | 24 +++- .../gr-autocomplete/gr-autocomplete.html | 1 + .../shared/gr-autocomplete/gr-autocomplete.js | 40 ++++++- .../gr-autocomplete/gr-autocomplete_test.html | 20 ++++ 6 files changed, 190 insertions(+), 19 deletions(-) diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html index f559794fed..fecb376994 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html @@ -15,8 +15,8 @@ limitations under the License. --> - + @@ -28,13 +28,14 @@ limitations under the License. form { display: flex; } - input { + gr-autocomplete { + background-color: white; border: 1px solid #d1d2d3; border-radius: 2px 0 0 2px; flex: 1; font: inherit; outline: none; - padding: 0 .25em; + padding: 0 .25em 0 .25em; } gr-button { background-color: #f1f2f3; @@ -43,7 +44,14 @@ limitations under the License. }
- + Search
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js index 7970169df0..8e52f8f83d 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js @@ -14,6 +14,70 @@ (function() { 'use strict'; + // Possible static search options for auto complete. + var SEARCH_OPERATORS = [ + 'added', + 'age', + 'age:1week', // Give an example age + 'author', + 'branch', + 'bug', + 'change', + 'comment', + 'commentby', + 'commit', + 'committer', + 'conflicts', + 'deleted', + 'delta', + 'file', + 'from', + 'has', + 'has:draft', + 'has:edit', + 'has:star', + 'has:stars', + 'intopic', + 'is', + 'is:abandoned', + 'is:closed', + 'is:draft', + 'is:mergeable', + 'is:merged', + 'is:open', + 'is:owner', + 'is:pending', + 'is:reviewed', + 'is:reviewer', + 'is:starred', + 'is:watched', + 'label', + 'message', + 'owner', + 'ownerin', + 'parentproject', + 'project', + 'projects', + 'query', + 'ref', + 'reviewedby', + 'reviewer', + 'reviewer:self', + 'reviewerin', + 'size', + 'star', + 'status', + 'status:abandoned', + 'status:closed', + 'status:draft', + 'status:merged', + 'status:open', + 'status:pending', + 'status:reviewed', + 'topic', + 'tr', + ]; + Polymer({ is: 'gr-search-bar', @@ -22,7 +86,6 @@ ], listeners: { - 'searchInput.keydown': '_inputKeyDownHandler', 'searchButton.tap': '_preventDefaultAndNavigateToInputVal', }, @@ -37,7 +100,12 @@ type: Object, value: function() { return document.body; }, }, - + query: { + type: Function, + value: function() { + return this._getSearchSuggestions.bind(this); + }, + }, _inputVal: String, }, @@ -45,10 +113,8 @@ this._inputVal = value; }, - _inputKeyDownHandler: function(e) { - if (e.keyCode == 13) { // Enter key - this._preventDefaultAndNavigateToInputVal(e); - } + _handleInputCommit: function(e) { + this._preventDefaultAndNavigateToInputVal(e); }, _preventDefaultAndNavigateToInputVal: function(e) { @@ -58,6 +124,36 @@ page.show('/q/' + encodeURIComponent(encodeURIComponent(this._inputVal))); }, + // TODO(kaspern): Flesh this out better. + _makeSuggestion: function(str) { + return { + name: str, + value: str, + }; + }, + + // TODO(kaspern): Expand support for more complicated autocomplete features. + _getSearchSuggestions: function(input) { + return Promise.resolve(SEARCH_OPERATORS).then(function(operators) { + if (!operators) { return []; } + var lowerCaseInput = input + .substring(input.lastIndexOf(' ') + 1) + .toLowerCase(); + return operators + .filter(function(operator) { + // Disallow autocomplete values that exactly match the whole str. + var opContainsInput = operator.indexOf(lowerCaseInput) !== -1; + var inputContainsOp = lowerCaseInput.indexOf(operator) !== -1; + return opContainsInput && !inputContainsOp; + }) + // Prioritize results that start with the input. + .sort(function(operator) { + return operator.indexOf(lowerCaseInput); + }) + .map(this._makeSuggestion); + }.bind(this)); + }, + _handleKey: function(e) { if (this.shouldSupressKeyboardShortcut(e)) { return; } switch (e.keyCode) { diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html index daa0c1a4ca..0c16774732 100644 --- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html +++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html @@ -68,15 +68,33 @@ limitations under the License. assert.notEqual(getActiveElement(), element.$.searchButton); done(); }); - MockInteractions.pressAndReleaseKeyOn(element.$.searchInput, 13); + MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); }); test('search query should be double-escaped', function() { var showStub = sinon.stub(page, 'show'); - element._inputVal = 'fate/stay'; - MockInteractions.pressAndReleaseKeyOn(element.$.searchInput, 13); + element.$.searchInput.text = 'fate/stay'; + MockInteractions.pressAndReleaseKeyOn(element.$.searchInput.$.input, 13); assert.equal(showStub.lastCall.args[0], '/q/fate%252Fstay'); showStub.restore(); }); + + test('_getSearchSuggestions returns proper set of suggestions', + function(done) { + element._getSearchSuggestions('is:o') + .then(function(suggestions) { + assert.equal(suggestions[0].name, 'is:open'); + assert.equal(suggestions[0].value, 'is:open'); + assert.equal(suggestions[1].name, 'is:owner'); + assert.equal(suggestions[1].value, 'is:owner'); + }) + .then(function() { + element._getSearchSuggestions('asdasdasdasd') + .then(function(suggestions) { + assert.equal(suggestions.length, 0); + done(); + }); + }); + }); }); diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html index da012075c9..278f002e36 100644 --- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html +++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html @@ -22,6 +22,7 @@ limitations under the License.