Update rebase dialog to give parent autocomplete suggestions

- The rest API is called once, with a query for recent changes.
- Input text is filtered against those changes for substring matches
  (includes the change number and change title).
- The current change does not show up in its own suggestion list.

Bug: Issue 4651
Change-Id: I4e87a5f6439999bce71c1a0ab0de42eae886fed2
This commit is contained in:
Becky Siegel
2018-01-09 14:07:34 -08:00
parent 9c97b6b8a3
commit c82b8f1941
6 changed files with 175 additions and 4 deletions

View File

@@ -132,6 +132,7 @@ limitations under the License.
<gr-overlay id="overlay" with-backdrop>
<gr-confirm-rebase-dialog id="confirmRebase"
class="confirmDialog"
change-number="[[change._number]]"
on-confirm="_handleRebaseConfirm"
on-cancel="_handleConfirmDialogCancel"
branch="[[change.branch]]"

View File

@@ -173,7 +173,7 @@
*/
properties: {
/** @type {{ branch: string, project: string }} */
/** @type {{ _number: number, branch: string, project: string }} */
change: Object,
actions: {
type: Object,
@@ -802,6 +802,7 @@
switch (key) {
case RevisionActions.REBASE:
this._showActionDialog(this.$.confirmRebase);
this.$.confirmRebase.fetchRecentChanges();
break;
case RevisionActions.CHERRYPICK:
this._handleCherrypickTap();

View File

@@ -306,6 +306,9 @@ limitations under the License.
test('rebase change', done => {
const fireActionStub = sandbox.stub(element, '_fireAction');
const fetchChangesStub = sandbox.stub(element.$.confirmRebase,
'fetchRecentChanges').returns(Promise.resolve([]));
element._hasKnownChainState = true;
flush(() => {
const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
MockInteractions.tap(rebaseButton);
@@ -318,6 +321,7 @@ limitations under the License.
method: 'POST',
title: 'Rebase onto tip of branch or parent change',
};
assert.isTrue(fetchChangesStub.called);
// rebase on other
element.$.confirmRebase.base = '1234';
element._handleRebaseConfirm();
@@ -340,6 +344,22 @@ limitations under the License.
});
});
test(`rebase dialog gets recent changes each time it's opened`, done => {
const fetchChangesStub = sandbox.stub(element.$.confirmRebase,
'fetchRecentChanges').returns(Promise.resolve([]));
element._hasKnownChainState = true;
const rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
MockInteractions.tap(rebaseButton);
assert.isTrue(fetchChangesStub.calledOnce);
flush(() => {
element.$.confirmRebase.fire('cancel');
MockInteractions.tap(rebaseButton);
assert.isTrue(fetchChangesStub.calledTwice);
done();
});
});
test('two dialogs are not shown at the same time', done => {
element._hasKnownChainState = true;
flush(() => {

View File

@@ -15,7 +15,9 @@ limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-confirm-rebase-dialog">
@@ -49,6 +51,7 @@ limitations under the License.
}
</style>
<gr-confirm-dialog
id="confirmDialog"
confirm-label="Rebase"
on-confirm="_handleConfirmTap"
on-cancel="_handleCancelTap">
@@ -98,15 +101,18 @@ limitations under the License.
</label>
</div>
<div class="parentRevisionContainer">
<input is="iron-input"
type="text"
<gr-autocomplete
id="parentInput"
bind-value="{{base}}"
query="[[_query]]"
text="{{_inputText}}"
on-tap="_handleEnterChangeNumberTap"
on-commit="_handleBaseSelected"
placeholder="Change number">
</gr-autocomplete>
</div>
</div>
</gr-confirm-dialog>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-confirm-rebase-dialog.js"></script>
</dom-module>

View File

@@ -36,14 +36,62 @@
* @type {?string} */
base: String,
branch: String,
changeNumber: Number,
hasParent: Boolean,
rebaseOnCurrent: Boolean,
_inputText: String,
_query: {
type: Function,
value() {
return this._getChangeSuggestions.bind(this);
},
},
_recentChanges: Array,
},
observers: [
'_updateSelectedOption(rebaseOnCurrent, hasParent)',
],
// This is called by gr-change-actions every time the rebase dialog is
// re-opened. Unlike other autocompletes that make a request with each
// updated input, this one gets all recent changes once and then filters
// them by the input. The query is re-run each time the dialog is opened
// in case there are new/updated changes in the generic query since the
// last time it was run.
fetchRecentChanges() {
return this.$.restAPI.getChanges(null, `is:open -age:90d`)
.then(response => {
const changes = [];
for (const key in response) {
if (!response.hasOwnProperty(key)) { continue; }
changes.push({
name: `${response[key]._number}: ${response[key].subject}`,
value: response[key]._number,
});
}
this._recentChanges = changes;
return this._recentChanges;
});
},
_getRecentChanges() {
if (this._recentChanges) {
return Promise.resolve(this._recentChanges);
}
return this.fetchRecentChanges();
},
_getChangeSuggestions(input) {
return this._getRecentChanges().then(changes =>
this._filterChanges(input, changes));
},
_filterChanges(input, changes) {
return changes.filter(change => change.name.includes(input) &&
change.value !== this.changeNumber);
},
_displayParentOption(rebaseOnCurrent, hasParent) {
return hasParent && rebaseOnCurrent;
},
@@ -58,11 +106,13 @@
_handleConfirmTap(e) {
e.preventDefault();
this._inputText = '';
this.fire('confirm', null, {bubbles: false});
},
_handleCancelTap(e) {
e.preventDefault();
this._inputText = '';
this.fire('cancel', null, {bubbles: false});
},
@@ -85,6 +135,10 @@
this.base = null;
},
_handleBaseSelected(e) {
this.base = e.detail.value;
},
_handleEnterChangeNumberTap() {
this.$.rebaseOnOtherInput.checked = true;
},

View File

@@ -34,9 +34,15 @@ limitations under the License.
<script>
suite('gr-confirm-rebase-dialog tests', () => {
let element;
let sandbox;
setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
});
teardown(() => {
sandbox.restore();
});
test('controls with parent and rebase on current available', () => {
@@ -82,5 +88,88 @@ limitations under the License.
assert.isTrue(element.$.rebaseOnTip.hasAttribute('hidden'));
assert.isFalse(element.$.tipUpToDateMsg.hasAttribute('hidden'));
});
test('input cleared on cancel or submit', () => {
element._inputText = '123';
element.$.confirmDialog.fire('confirm');
assert.equal(element._inputText, '');
element._inputText = '123';
element.$.confirmDialog.fire('cancel');
assert.equal(element._inputText, '');
});
suite('parent suggestions', () => {
let recentChanges;
setup(() => {
recentChanges = [
{
name: '123: my first awesome change',
value: 123,
},
{
name: '124: my second awesome change',
value: 124,
},
{
name: '245: my third awesome change',
value: 245,
},
];
sandbox.stub(element.$.restAPI, 'getChanges').returns(Promise.resolve(
[
{
_number: 123,
subject: 'my first awesome change',
},
{
_number: 124,
subject: 'my second awesome change',
},
{
_number: 245,
subject: 'my third awesome change',
},
]
));
});
test('_getRecentChanges', () => {
sandbox.spy(element, '_getRecentChanges');
return element._getRecentChanges().then(() => {
assert.deepEqual(element._recentChanges, recentChanges);
assert.equal(element.$.restAPI.getChanges.callCount, 1);
// When called a second time, should not re-request recent changes.
element._getRecentChanges();
}).then(() => {
assert.equal(element._getRecentChanges.callCount, 2);
assert.equal(element.$.restAPI.getChanges.callCount, 1);
});
});
test('_filterChanges', () => {
assert.equal(element._filterChanges('123', recentChanges).length, 1);
assert.equal(element._filterChanges('12', recentChanges).length, 2);
assert.equal(element._filterChanges('awesome', recentChanges).length,
3);
assert.equal(element._filterChanges('third', recentChanges).length,
1);
element.changeNumber = 123;
assert.equal(element._filterChanges('123', recentChanges).length, 0);
assert.equal(element._filterChanges('124', recentChanges).length, 1);
assert.equal(element._filterChanges('awesome', recentChanges).length,
2);
});
test('input text change triggers function', () => {
sandbox.spy(element, '_getRecentChanges');
element._inputText = '1';
assert.isTrue(element._getRecentChanges.calledOnce);
element._inputText = '12';
assert.isTrue(element._getRecentChanges.calledTwice);
});
});
});
</script>