Merge "Update rebase dialog to give parent autocomplete suggestions"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 481cd76..f3286f2 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -132,6 +132,7 @@
     <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]]"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index b2a6f0d..92dc887 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -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();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 62b4626..df2fbcb 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -306,6 +306,9 @@
 
     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 @@
           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 @@
       });
     });
 
+    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(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
index 582a03c..4370d7e 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html
@@ -15,7 +15,9 @@
 -->
 
 <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 @@
       }
     </style>
     <gr-confirm-dialog
+        id="confirmDialog"
         confirm-label="Rebase"
         on-confirm="_handleConfirmTap"
         on-cancel="_handleCancelTap">
@@ -98,15 +101,18 @@
           </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>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index eb0fa17..e4f6e97 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -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;
     },
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index 0ea7c49..ccfc368 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -34,9 +34,15 @@
 <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 @@
       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>