Add patchset navigator for checks table

Change-Id: I6d29739a68f9b49a98e76017f8ad8aad001fc01c
diff --git a/gr-checks/gr-checks-view.html b/gr-checks/gr-checks-view.html
index 205dbe6..b7c658d 100644
--- a/gr-checks/gr-checks-view.html
+++ b/gr-checks/gr-checks-view.html
@@ -48,6 +48,7 @@
       header {
         display: flex;
         margin: 1rem 1rem 0;
+        justify-content: space-between;
       }
 
       table {
@@ -96,9 +97,20 @@
       }
     </style>
 
-    <template is="dom-if" if="[[_createCheckerCapability]]">
-      <gr-button class="configure-button" on-click="_handleConfigureClicked"> Configure </gr-button>
-    </template>
+    <header>
+      <template is="dom-if" if="[[_patchSetDropdownItems.length]]">
+        <gr-dropdown-list
+          class="patch-set-dropdown"
+          items="[[_patchSetDropdownItems]]"
+          on-value-change="_handlePatchSetChanged"
+          value="[[_currentPatchSet]]">
+        </gr-dropdown-list>
+      </template>
+      <template is="dom-if" if="[[_createCheckerCapability]]">
+        <gr-button class="configure-button" on-click="_handleConfigureClicked"> Configure </gr-button>
+      </template>
+    </header>
+
 
     <template is="dom-if" if="[[_isLoading(_status)]]">
       <div class="no-content">
@@ -121,10 +133,6 @@
     </template>
 
     <template is="dom-if" if="[[_hasResults(_status)]]">
-      <header>
-        <h3>Latest checks for Patchset [[revision._number]]</h3>
-      </header>
-
       <table>
         <thead>
           <tr class="headerRow">
diff --git a/gr-checks/gr-checks-view.js b/gr-checks/gr-checks-view.js
index 41d4054..af2d11c 100644
--- a/gr-checks/gr-checks-view.js
+++ b/gr-checks/gr-checks-view.js
@@ -39,7 +39,10 @@
     is: 'gr-checks-view',
 
     properties: {
-      revision: Object,
+      revision: {
+        type: Object,
+        observer: '_computeCurrentPatchSet',
+      },
       change: Object,
       /** @type {function(number, number): !Promise<!Object>} */
       getChecks: Function,
@@ -61,15 +64,32 @@
         type: Boolean,
         value: false,
       },
+      _patchSetDropdownItems: {
+        type: Array,
+        value() { return []; },
+      },
+      _currentPatchSet: {
+        type: Number,
+      },
     },
 
     observers: [
-      '_pollChecksRegularly(change, revision, getChecks)',
+      '_pollChecksRegularly(change, _currentPatchSet, getChecks)',
     ],
 
     attached() {
       this.pluginRestApi = this.plugin.restApi();
       this._initCreateCheckerCapability();
+      this._patchSetDropdownItems = Object.values(this.change.revisions)
+          .filter(patch => patch._number !== 'edit')
+          .map(patch => {
+            return {
+              text: 'Patchset ' + patch._number,
+              value: patch._number,
+            };
+          })
+          .sort((a, b) => b.value - a.value);
+      this._currentPatchSet = this.revision._number;
     },
 
     detached() {
@@ -77,6 +97,16 @@
       this.unlisten(document, 'visibilitychange', '_onVisibililityChange');
     },
 
+    _computeCurrentPatchSet(revision) {
+      this._currentPatchSet = revision._number;
+    },
+
+    _handlePatchSetChanged(e) {
+      const patchSet = e.detail.value;
+      if (patchSet === this._currentPatchSet) return;
+      this._currentPatchSet = patchSet;
+    },
+
     _handleCheckersListResize() {
       // Force polymer to recalculate position of overlay when length of
       // checkers changes
@@ -160,10 +190,11 @@
      * @param {!Defs.Revision} revision
      * @param {function(number, number): !Promise<!Object>} getChecks
      */
-    _fetchChecks(change, revision, getChecks) {
-      if (!getChecks || !change || !revision) return;
+    _fetchChecks(change, revisionNumber, getChecks) {
+      if (!getChecks || !change || !revisionNumber) return;
 
-      getChecks(change._number, revision._number).then(checks => {
+      getChecks(change._number, revisionNumber).then(checks => {
+        if (revisionNumber !== this._currentPatchSet) return;
         if (checks && checks.length) {
           checks.sort((a, b) => this._orderChecks(a, b));
           if (!this._checks) {
@@ -186,7 +217,8 @@
         clearInterval(this.pollChecksInterval);
         return;
       }
-      this._pollChecksRegularly(this.change, this.revision, this.getChecks);
+      this._pollChecksRegularly(this.change, this._currentPatchSet,
+          this.getChecks);
     },
 
     _toggleCheckMessage(e) {
@@ -205,11 +237,12 @@
           !this._checks[idx].showCheckMessage);
     },
 
-    _pollChecksRegularly(change, revision, getChecks) {
+    _pollChecksRegularly(change, revisionNumber, getChecks) {
+      if (!change || !revisionNumber || !getChecks) return;
       if (this.pollChecksInterval) {
         clearInterval(this.pollChecksInterval);
       }
-      const poll = () => this._fetchChecks(change, revision, getChecks);
+      const poll = () => this._fetchChecks(change, revisionNumber, getChecks);
       poll();
       this.pollChecksInterval = setInterval(poll, CHECKS_POLL_INTERVAL_MS);
       if (!this.visibilityChangeListenerAdded) {
diff --git a/gr-checks/gr-checks-view_test.html b/gr-checks/gr-checks-view_test.html
index a801b9f..2ad1b3d 100644
--- a/gr-checks/gr-checks-view_test.html
+++ b/gr-checks/gr-checks-view_test.html
@@ -8,7 +8,6 @@
 <title>gr-checks-item</title>
 <link rel="import" href="gr-checks-view.html">
 
-
 <!-- Gr-overlay does not exist in the test framework
 It is expected to be provided by Gerrit core -->
 <dom-module id="gr-overlay">
@@ -30,7 +29,7 @@
       get-checks="[[getChecks]]"
       is-configured="[[isConfigured]]"
       plugin="[[plugin]]"
-      retry-check="[[retryCheck]]">
+      retry-check="[[retryCheck]]"
     </gr-checks-view>
   </template>
 </test-fixture>
@@ -79,6 +78,23 @@
       commit: '1c9a1dfd38ea51dc7880f3ddf669100710f0c91b',
     },
   };
+  const REVISION2 = {
+    kind: 'REWORK',
+    _number: 2,
+    created: '2018-05-15 21:56:13.000000000',
+    uploader: {
+      _account_id: 1000000,
+    },
+    ref: 'refs/changes/00/1000/2',
+    commit: {
+      parents: [],
+      subject: '',
+      message: '\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n',
+      commit: '1c9a1dfd38ea51dc6880f3ddf669100710f0c91b',
+    },
+  };
+
+  const CHECKS_POLL_INTERVAL_MS = 60 * 1000;
 
   suite('gr-checks-view tests', () => {
     let element;
@@ -143,7 +159,7 @@
           project: 'test-repository',
           _number: 2,
           revisions: {
-            'first-sha': 'test-revision',
+            'first-sha': REVISION2,
             'second-sha': REVISION,
           },
         },
@@ -198,6 +214,7 @@
             getAccountCapabilitiesResolve({'checks-administrateCheckers': false});
             flush(done);
           });
+
           test('checker button does not render', () => {
             assert(!element.$$('gr-button'));
           });
@@ -301,11 +318,6 @@
         assert.isFalse(isConfiguredSpy.called);
       });
 
-      test('renders the header', () => {
-        const header = element.$$('header > h3');
-        assert.equal(header.textContent.trim(), 'Latest checks for Patchset 3');
-      });
-
       test('renders a table of all the checks', () => {
         const tbody = element.$$('table > tbody');
         assert.lengthOf(tbody.querySelectorAll('gr-checks-item'), 3);
@@ -328,10 +340,43 @@
           getAccountCapabilitiesResolve({'checks-administrateCheckers': true});
           flush(done);
         });
+
         test('checker button renders', () => {
           assert(element.$$('gr-button'));
         });
       });
+
+      suite('patchset navigation', () => {
+        test('renders the dropdown', () => {
+          assert.isNotNull(element.querySelector('gr-dropdown-list'));
+        });
+
+        test('when patchset updated it fetches new checks', done => {
+          const clock = sinon.useFakeTimers();
+          const fetchChecksStub = sandbox.stub(element,
+              '_fetchChecks');
+          assert.equal(element._currentPatchSet, 3);
+          element._currentPatchSet = 2;
+          const firstCallArgs = fetchChecksStub.args[0];
+          assert.equal(firstCallArgs[1], element._currentPatchSet);
+          clock.tick(CHECKS_POLL_INTERVAL_MS + 1000);
+          flush(() => {
+            assert(fetchChecksStub.callCount === 2);
+            const secondCallArgs = fetchChecksStub.args[1];
+            assert.equal(secondCallArgs[1], element._currentPatchSet);
+            done();
+          });
+        });
+
+        test('update to revision updatesd currentPatchset', done => {
+          assert.equal(element._currentPatchSet, 3);
+          element.revision = REVISION2;
+          flush(() => {
+            assert.equal(element._currentPatchSet, 2);
+            done();
+          });
+        });
+      });
     });
   });