Make edit file input an autocomplete

Uses the /files endpoint to query possible files.

Bug: Issue 4437
Change-Id: I439100b5f85de05cba8988daa3fd71502b6af07f
This commit is contained in:
Kasper Nilsson
2017-10-05 15:21:50 -07:00
parent c98af21c4b
commit c4e64fc8f9
7 changed files with 94 additions and 44 deletions

View File

@@ -16,13 +16,14 @@ limitations under the License.
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -41,21 +42,21 @@ limitations under the License.
margin-left: 1em;
text-decoration: none;
}
paper-input {
--paper-input-container: {
padding: 0;
min-width: 15em;
}
--paper-input-container-input: {
font-size: 1em;
}
}
gr-confirm-dialog {
width: 50em;
}
gr-confirm-dialog .main {
width: 100%;
}
gr-autocomplete {
--gr-autocomplete: {
border: 1px solid #d1d2d3;
border-radius: 2px;
font-size: 1em;
height: 2em;
padding: 0 .15em;
}
}
</style>
<template is="dom-repeat" items="[[_actions]]" as="action">
<gr-button
@@ -74,13 +75,15 @@ limitations under the License.
<div class="header">Edit a file</div>
<div class="main">
<!-- TODO(kaspern): Make this an autocomplete. -->
<paper-input
<gr-autocomplete
class="input"
label="Enter an existing or new full file path."
value="{{_path}}"></paper-input>
placeholder="Enter an existing or new full file path."
query="[[_query]]"
text="{{_path}}"></gr-autocomplete>
</div>
</gr-confirm-dialog>
</gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-edit-controls.js"></script>
</dom-module>

View File

@@ -37,8 +37,18 @@
type: String,
value: '',
},
_query: {
type: Function,
value() {
return this._queryFiles.bind(this);
},
},
},
behaviors: [
Gerrit.PatchSetBehavior,
],
_handleTap(e) {
e.preventDefault();
const action = Polymer.dom(e).localTarget.id;
@@ -73,7 +83,8 @@
},
_closeDialog(dialog) {
dialog.querySelectorAll('.input').forEach(input => { input.value = ''; });
dialog.querySelectorAll('gr-autocomplete')
.forEach(input => { input.text = ''; });
dialog.classList.toggle('invisible', true);
return this.$.overlay.close();
},
@@ -87,5 +98,12 @@
Gerrit.Nav.navigateToRelativeUrl(url);
this._closeDialog(Polymer.dom(e).localTarget);
},
_queryFiles(input) {
return this.$.restAPI.queryChangeFiles(this.change._number,
this.EDIT_NAME, input).then(res => res.map(file => {
return {name: file};
}));
},
});
})();

View File

@@ -37,12 +37,16 @@ suite('gr-edit-controls tests', () => {
let sandbox;
let showDialogSpy;
let closeDialogSpy;
let queryStub;
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element.change = {_number: '42'};
showDialogSpy = sandbox.spy(element, '_showDialog');
closeDialogSpy = sandbox.spy(element, '_closeDialog');
queryStub = sandbox.stub(element.$.restAPI, 'queryChangeFiles')
.returns(Promise.resolve([]));
flushAsynchronousOperations();
});
@@ -67,7 +71,9 @@ suite('gr-edit-controls tests', () => {
MockInteractions.tap(element.$$('#edit'));
return showDialogSpy.lastCall.returnValue.then(() => {
assert.isTrue(element.$.editDialog.disabled);
element._path = 'src/test.cpp';
assert.isFalse(queryStub.called);
element.$.editDialog.querySelector('.input').text = 'src/test.cpp';
assert.isTrue(queryStub.called);
assert.isFalse(element.$.editDialog.disabled);
MockInteractions.tap(element.$.editDialog.$$('gr-button[primary]'));
for (const stub of navStubs) { assert.isTrue(stub.called); }
@@ -79,7 +85,7 @@ suite('gr-edit-controls tests', () => {
MockInteractions.tap(element.$$('#edit'));
return showDialogSpy.lastCall.returnValue.then(() => {
assert.isTrue(element.$.editDialog.disabled);
element._path = 'src/test.cpp';
element.$.editDialog.querySelector('.input').text = 'src/test.cpp';
assert.isFalse(element.$.editDialog.disabled);
MockInteractions.tap(element.$.editDialog.$$('gr-button'));
for (const stub of navStubs) { assert.isFalse(stub.called); }
@@ -92,7 +98,7 @@ suite('gr-edit-controls tests', () => {
test('openEditDialog', () => {
return element.openEditDialog('test/path.cpp').then(() => {
assert.isFalse(element.$.editDialog.hasAttribute('hidden'));
assert.equal(element.$.editDialog.querySelector('.input').value,
assert.equal(element.$.editDialog.querySelector('.input').text,
'test/path.cpp');
});
});

View File

@@ -46,6 +46,7 @@
},
suggestions: {
type: Array,
value: () => [],
observer: '_resetCursorStops',
},
_suggestionEls: {
@@ -151,8 +152,12 @@
},
_resetCursorStops() {
Polymer.dom.flush();
this._suggestionEls = this.$.suggestions.querySelectorAll('li');
if (this.suggestions.length > 0) {
Polymer.dom.flush();
this._suggestionEls = this.$.suggestions.querySelectorAll('li');
} else {
this._suggestionEls = [];
}
},
_resetCursorIndex() {

View File

@@ -38,31 +38,29 @@ limitations under the License.
color: red;
}
</style>
<div>
<input
id="input"
class$="[[_computeClass(borderless)]]"
is="iron-input"
disabled$="[[disabled]]"
bind-value="{{text}}"
placeholder="[[placeholder]]"
on-keydown="_handleKeydown"
on-focus="_onInputFocus"
on-blur="_onInputBlur"
autocomplete="off"/>
<gr-autocomplete-dropdown
vertical-align="top"
vertical-offset="20"
horizontal-align="auto"
id="suggestions"
on-item-selected="_handleItemSelect"
on-keydown="_handleKeydown"
suggestions="[[_suggestions]]"
role="listbox"
index="[[_index]]"
position-target="[[_inputElement]]">
</gr-autocomplete-dropdown>
</div>
<input
id="input"
class$="[[_computeClass(borderless)]]"
is="iron-input"
disabled$="[[disabled]]"
bind-value="{{text}}"
placeholder="[[placeholder]]"
on-keydown="_handleKeydown"
on-focus="_onInputFocus"
on-blur="_onInputBlur"
autocomplete="off"/>
<gr-autocomplete-dropdown
vertical-align="top"
vertical-offset="20"
horizontal-align="auto"
id="suggestions"
on-item-selected="_handleItemSelect"
on-keydown="_handleKeydown"
suggestions="[[_suggestions]]"
role="listbox"
index="[[_index]]"
position-target="[[_inputElement]]">
</gr-autocomplete-dropdown>
</template>
<script src="gr-autocomplete.js"></script>
</dom-module>

View File

@@ -904,6 +904,17 @@
patchRange.patchNum);
},
/**
* @param {number|string} changeNum
* @param {number|string} patchNum
* @param {string} query
* @return {!Promise<!Object>}
*/
queryChangeFiles(changeNum, patchNum, query) {
return this._getChangeURLAndFetch(changeNum,
`/files?q=${encodeURIComponent(query)}`, patchNum);
},
getChangeFilesAsSpeciallySortedArray(changeNum, patchRange) {
return this.getChangeFiles(changeNum, patchRange).then(
this._normalizeChangeFilesResponse.bind(this));

View File

@@ -701,6 +701,15 @@ limitations under the License.
assert.equal(sendStub.lastCall.args[1], '/projects/x%2Fy');
});
test('queryChangeFiles', () => {
const fetchStub = sandbox.stub(element, '_getChangeURLAndFetch')
.returns(Promise.resolve());
return element.queryChangeFiles('42', 'edit', 'test/path.js').then(() => {
assert.deepEqual(fetchStub.lastCall.args,
['42', '/files?q=test%2Fpath.js', 'edit']);
});
});
test('getProjects', () => {
sandbox.stub(element, '_fetchSharedCacheURL');
element.getProjects('test', 25);