Merge changes I29bc8ee1,Ica77b8f5,I05ceb86c,I3be67af9,I542286a7, ...

* changes:
  CheckerInput: Rename field 'blockingConditions' to 'blocking'
  Test listing pending checks for checker that has an invalid query
  ListChecksIT: Add tests with non-current patch set
  GetCheckIT: Test more special cases
  Fix binding of ChecksSubmitRule
  Test that getting a check with options works if checker is invalid
  GetCheckIT: Only assert things that are relevant to the tests
  ListChecksIT: Let each test care about the test setup itself
  CheckersByRepositoryNotes: Fix loading checkers from old revision
  Add unit test for CheckersByRepositoryNotes
  Test that setting an invalid URL on checks is not allowed
  CreateCheckIT: Add more tests
  CheckInput: Mark all fields as Nullable
  Fix update finished field in check and add more update check tests
  Consistently use the name 'repository' in the checks API
  CheckerConfig: Set new updated timestamp only when config has changed
  CheckerConfig: Don't reload config file on save
  CheckerConfig#getConfigForTesting: Return Optional<Config>
  Rename createdOn/updateOn in CheckerInfo to created/updated
  CheckerUpdate: Remove support for specifying updated timestamp
  Test that creation/last-updated timestamps for checks/checkers are reasonable
diff --git a/gr-checks/BUILD b/gr-checks/BUILD
new file mode 100644
index 0000000..e2dd6bd
--- /dev/null
+++ b/gr-checks/BUILD
@@ -0,0 +1,19 @@
+package_group(
+    name = "visibility",
+    packages = ["//plugins/checks/..."],
+)
+
+package(default_visibility = [":visibility"])
+
+load("//tools/bzl:js.bzl", "bundle_assets", "polygerrit_plugin")
+
+polygerrit_plugin(
+    name = "gr-checks",
+    app = "gr-checks.html",
+    plugin_name = "checks",
+    externs = ["externs.js"],
+    srcs = glob([
+            "*.html",
+            "*.js",
+        ]),
+)
diff --git a/gr-checks/externs.js b/gr-checks/externs.js
new file mode 100644
index 0000000..f14959b
--- /dev/null
+++ b/gr-checks/externs.js
@@ -0,0 +1,6 @@
+/**
+ * @fileoverview externs for moment.js
+ * @externs
+ */
+
+function moment() {};
diff --git a/gr-checks/gr-checks-all-statuses.js b/gr-checks/gr-checks-all-statuses.js
new file mode 100644
index 0000000..9f76441
--- /dev/null
+++ b/gr-checks/gr-checks-all-statuses.js
@@ -0,0 +1,72 @@
+(function() {
+'use strict';
+
+window.Gerrit = (window.Gerrit || {});
+window.Gerrit.Checks = (window.Gerrit.Checks || {});
+
+// Prevent redefinition.
+if (window.Gerrit.Checks.Statuses) return;
+
+const Statuses = {
+  // non-terminal statuses
+  STATUS_UNKNOWN: 'STATUS_UNKNOWN',
+  QUEUING: 'QUEUING',
+  QUEUED: 'QUEUED',
+  WORKING: 'WORKING',
+
+  // terminal statuses
+  SUCCESS: 'SUCCESS',
+  FAILURE: 'FAILURE',
+  INTERNAL_ERROR: 'INTERNAL_ERROR',
+  TIMEOUT: 'TIMEOUT',
+  CANCELLED: 'CANCELLED',
+};
+
+
+function isStatus(status, includedStatuses) {
+  return includedStatuses.includes(status);
+}
+
+
+function isUnevaluated(status) {
+  return isStatus(status, [Statuses.STATUS_UNKNOWN, Statuses.CANCELLED]);
+}
+
+function isInProgress(status) {
+  return isStatus(
+      status, [Statuses.QUEUING, Statuses.QUEUED, Statuses.WORKING]);
+}
+
+function isSuccessful(status) {
+  return isStatus(status, [Statuses.SUCCESS]);
+}
+
+function isFailed(status) {
+  return isStatus(
+      status, [Statuses.FAILURE, Statuses.INTERNAL_ERROR, Statuses.TIMEOUT]);
+}
+
+
+function statusClass(status) {
+  if (isUnevaluated(status)) {
+    return 'unevaluated';
+  }
+  if (isInProgress(status)) {
+    return 'in-progress';
+  }
+  if (isSuccessful(status)) {
+    return 'successful';
+  }
+  if (isFailed(status)) {
+    return 'failed';
+  }
+  return 'unevaluated';
+}
+
+window.Gerrit.Checks.Statuses = Statuses;
+window.Gerrit.Checks.isUnevaluated = isUnevaluated;
+window.Gerrit.Checks.isInProgress = isInProgress;
+window.Gerrit.Checks.isSuccessful = isSuccessful;
+window.Gerrit.Checks.isFailed = isFailed;
+window.Gerrit.Checks.statusClass = statusClass;
+})();
diff --git a/gr-checks/gr-checks-change-list-header-view.html b/gr-checks/gr-checks-change-list-header-view.html
new file mode 100644
index 0000000..9ddc13a
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-header-view.html
@@ -0,0 +1,13 @@
+<dom-module id="gr-checks-change-list-header-view">
+  <style>
+    :host {
+      display: table-cell;
+      padding: 0 3px;
+    }
+  </style>
+  <template>
+    Checks
+  </template>
+
+  <script src="gr-checks-change-list-header-view.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-change-list-header-view.js b/gr-checks/gr-checks-change-list-header-view.js
new file mode 100644
index 0000000..7a6a773
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-header-view.js
@@ -0,0 +1,7 @@
+(function() {
+'use strict';
+
+Polymer({
+  is: 'gr-checks-change-list-header-view',
+});
+})();
diff --git a/gr-checks/gr-checks-change-list-header-view_test.html b/gr-checks/gr-checks-change-list-header-view_test.html
new file mode 100644
index 0000000..d9113f5
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-header-view_test.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-change-list-header-view</title>
+<link rel="import" href="webcomponent_lib/gr-checks-change-list-header-view.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-change-list-header-view></gr-checks-change-list-header-view>
+  </template>
+</test-fixture>
+
+<script>
+
+  suite('gr-checks-change-list-header-view tests', () => {
+    let element;
+    let sandbox;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+
+
+      element = fixture('basic');
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('renders the header', () => {
+      assert.include(element.textContent.trim(), 'Checks');
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-change-list-item-cell-view.html b/gr-checks/gr-checks-change-list-item-cell-view.html
new file mode 100644
index 0000000..61af378
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-item-cell-view.html
@@ -0,0 +1,13 @@
+<dom-module id="gr-checks-change-list-item-cell-view">
+  <style>
+    :host {
+      display: inline-block;
+      text-align: center;
+      width: 100%;
+    }
+  </style>
+  <template>
+  </template>
+
+  <script src="gr-checks-change-list-item-cell-view.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-change-list-item-cell-view.js b/gr-checks/gr-checks-change-list-item-cell-view.js
new file mode 100644
index 0000000..4d45a44
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-item-cell-view.js
@@ -0,0 +1,11 @@
+(function() {
+'use strict';
+
+Polymer({
+  is: 'gr-checks-change-list-item-cell-view',
+
+  properties: {
+    change: Object,
+  },
+});
+})();
diff --git a/gr-checks/gr-checks-change-list-item-cell-view_test.html b/gr-checks/gr-checks-change-list-item-cell-view_test.html
new file mode 100644
index 0000000..c50f7e6
--- /dev/null
+++ b/gr-checks/gr-checks-change-list-item-cell-view_test.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-change-list-item-cell-view</title>
+<link rel="import" href="webcomponent_lib/gr-checks-change-list-item-cell-view.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-change-list-item-cell-view change="[[change]]"></gr-checks-change-list-item-cell-view>
+  </template>
+</test-fixture>
+
+<script>
+
+  suite('gr-checks-change-list-item-cell-view tests', () => {
+    let element;
+    let sandbox;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+
+
+      element = fixture('basic', {
+        change: {
+          'project': 'test-repository',
+          'revisions': {
+            'first-sha': "test-revision",
+            'second-sha': "test-revision2",
+          }
+        },
+      });
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('renders the element', () => {
+      assert.exists(element);
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-change-view-tab-header-view.html b/gr-checks/gr-checks-change-view-tab-header-view.html
new file mode 100644
index 0000000..e5d6dc1
--- /dev/null
+++ b/gr-checks/gr-checks-change-view-tab-header-view.html
@@ -0,0 +1,7 @@
+<dom-module id="gr-checks-change-view-tab-header-view">
+  <template>
+    Checks
+  </template>
+
+  <script src="gr-checks-change-view-tab-header-view.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-change-view-tab-header-view.js b/gr-checks/gr-checks-change-view-tab-header-view.js
new file mode 100644
index 0000000..b18ce2f
--- /dev/null
+++ b/gr-checks/gr-checks-change-view-tab-header-view.js
@@ -0,0 +1,7 @@
+(function() {
+'use strict';
+
+Polymer({
+  is: 'gr-checks-change-view-tab-header-view',
+});
+})();
diff --git a/gr-checks/gr-checks-change-view-tab-header-view_test.html b/gr-checks/gr-checks-change-view-tab-header-view_test.html
new file mode 100644
index 0000000..9915502
--- /dev/null
+++ b/gr-checks/gr-checks-change-view-tab-header-view_test.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-change-view-tab-header-view</title>
+<link rel="import" href="webcomponent_lib/gr-checks-change-view-tab-header-view.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-change-view-tab-header-view></gr-checks-change-view-tab-header-view>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-checks-change-view-tab-header-view tests', () => {
+    let element;
+    let sandbox;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+
+
+      element = fixture('basic');
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('renders the header', () => {
+      assert.include(element.textContent.trim(), 'Checks');
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-chip-view.html b/gr-checks/gr-checks-chip-view.html
new file mode 100644
index 0000000..2bda0de
--- /dev/null
+++ b/gr-checks/gr-checks-chip-view.html
@@ -0,0 +1,31 @@
+<dom-module id="gr-checks-chip-view">
+  <template>
+    <style>
+
+      :host {
+        display: inline-block;
+      }
+
+      .chip {
+        border-color: #D0D0D0;
+        border-radius: 4px;
+        border-style: solid;
+        border-width: 1px;
+        padding: 4px 8px;
+      }
+      .chip.failed {
+        border-color: #DA4236;
+      }
+    </style>
+    <template is="dom-if" if="[[_hasChecks]]">
+      Checks:
+      <span class$="[[_chipClasses]]">
+        <gr-checks-status status="[[_status]]"></gr-checks-status>
+        [[_statusString]]
+      </span>
+    </template>
+  </template>
+
+  <script src="gr-checks-all-statuses.js"></script>
+  <script src="gr-checks-chip-view.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-chip-view.js b/gr-checks/gr-checks-chip-view.js
new file mode 100644
index 0000000..415bc1f
--- /dev/null
+++ b/gr-checks/gr-checks-chip-view.js
@@ -0,0 +1,124 @@
+(function() {
+'use strict';
+const Statuses = window.Gerrit.Checks.Statuses;
+
+const StatusPriorityOrder = [
+  Statuses.INTERNAL_ERROR, Statuses.TIMEOUT, Statuses.FAILURE,
+  Statuses.STATUS_UNKNOWN, Statuses.CANCELLED, Statuses.QUEUED,
+  Statuses.QUEUING, Statuses.WORKING, Statuses.SUCCESS
+];
+
+const HumanizedStatuses = {
+  // non-terminal statuses
+  STATUS_UNKNOWN: 'unevaluated',
+  QUEUING: 'in progress',
+  QUEUED: 'in progress',
+  WORKING: 'in progress',
+
+  // terminal statuses
+  SUCCESS: 'successful',
+  FAILURE: 'failed',
+  INTERNAL_ERROR: 'failed',
+  TIMEOUT: 'failed',
+  CANCELLED: 'unevaluated',
+};
+
+
+const Defs = {};
+/**
+ * @typedef {{
+ *   revisions: !Object<string, !Object>,
+ * }}
+ */
+Defs.Change;
+
+/**
+ * @param {!Defs.Change} change The current CL.
+ * @param {!Object} revision The current patchset.
+ * @return {string|undefined}
+ */
+function currentRevisionSha(change, revision) {
+  return Object.keys(change.revisions)
+      .find(sha => change.revisions[sha] === revision);
+}
+
+function computeCheckStatuses(checks) {
+  return checks.reduce((accum, check) => {
+    accum[check.state] || (accum[check.state] = 0);
+    accum[check.state]++;
+    return accum;
+  }, {total: checks.length});
+}
+
+Polymer({
+  is: 'gr-checks-chip-view',
+
+  properties: {
+    revision: Object,
+    change: Object,
+    // TODO(brohlfs): Implement getChecks based on new Rest APIs.
+    /** @type {function(string, (string|undefined)): !Promise<!Object>} */
+    getChecks: Function,
+    _checkStatuses: Object,
+    _hasChecks: Boolean,
+    _status: {type: String, computed: '_computeStatus(_checkStatuses)'},
+    _statusString: {
+      type: String,
+      computed: '_computeStatusString(_status, _checkStatuses)'
+    },
+    _chipClasses: {type: String, computed: '_computeChipClass(_status)'},
+  },
+
+  observers: [
+    '_fetchChecks(change, revision, getChecks)',
+  ],
+
+  /**
+   * @param {!Defs.Change} change The current CL.
+   * @param {!Object} revision The current patchset.
+   * @param {function(string, (string|undefined)): !Promise<!Object>}
+   *     getChecks function to get checks.
+   */
+  _fetchChecks(change, revision, getChecks) {
+    const repository = change['project'];
+    const gitSha = currentRevisionSha(change, revision);
+
+    getChecks(repository, gitSha).then(checks => {
+      this.set('_hasChecks', checks.length > 0);
+      if (checks.length > 0) {
+        this.set(
+            '_checkStatuses', computeCheckStatuses(checks));
+      }
+    });
+  },
+
+  /**
+   * @param {!Object} checkStatuses The number of checks in each status.
+   * @return {string}
+   */
+  _computeStatus(checkStatuses) {
+    return StatusPriorityOrder.find(
+               status => checkStatuses[status] > 0) ||
+        Statuses.STATUS_UNKNOWN;
+  },
+
+  /**
+   * @param {string} status The overall status of the checks.
+   * @param {!Object} checkStatuses The number of checks in each status.
+   * @return {string}
+   */
+  _computeStatusString(status, checkStatuses) {
+    if (checkStatuses.total === 0) return 'No checks';
+    return `${checkStatuses[status]} of ${
+        checkStatuses.total} checks ${HumanizedStatuses[status]}`;
+  },
+
+  /**
+   * @param {string} status The overall status of the checks.
+   * @return {string}
+   */
+  _computeChipClass(status) {
+    return `chip ${window.Gerrit.Checks.statusClass(status)}`;
+  },
+});
+})();
diff --git a/gr-checks/gr-checks-chip-view_test.html b/gr-checks/gr-checks-chip-view_test.html
new file mode 100644
index 0000000..fb1c67a
--- /dev/null
+++ b/gr-checks/gr-checks-chip-view_test.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-chip-view</title>
+<link rel="import" href="webcomponent_lib/gr-checks-chip-view.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-chip-view change="[[change]]" revision="[[revision]]" get-gr-checks="[[getChecks]]"></gr-checks-chip-view>
+  </template>
+</test-fixture>
+
+<script>
+  const CHECK1 = {
+    checkId: 'test-check-id',
+    logUrl: 'http://example.com/test-log-url',
+    startTime: "2019-02-06T22:25:19.269Z",
+    finishTime: "2019-02-06T22:25:44.574Z",
+    status: 'SUCCESS',
+  };
+
+  const CHECK2 = {
+    checkId: 'test-check-id-2',
+    logUrl: 'http://example.com/test-log-url',
+    startTime: "2019-02-06T22:25:19.269Z",
+    finishTime: "2019-02-06T22:25:44.574Z",
+    status: 'FAILURE',
+  };
+
+  suite('gr-checks-chip-view tests', () => {
+    let element;
+    let sandbox;
+    let getChecksSpy;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+
+      getChecksSpy = sinon.stub();
+      getChecksSpy.returns(Promise.resolve([CHECK1, CHECK2, CHECK1]));
+
+      element = fixture('basic', {
+        getChecks: getChecksSpy,
+        change: {
+          'project': 'test-repository',
+          'revisions': {
+            'first-sha': "test-revision",
+            'second-sha': "test-revision2",
+          }
+        },
+        revision: 'test-revision2',
+      });
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('renders the checks prefix', () => {
+      assert.include(element.textContent.trim(), 'Checks:');
+    });
+
+    suite('builds chip contents', () => {
+      test('queries the checks', () => {
+        assert.isTrue(getChecksSpy.called);
+        assert.isTrue(getChecksSpy.calledWith('test-repository', 'second-sha'));
+      });
+
+      test('renders the text of failed checks', () => {
+        const chip = element.$$('.chip');
+        assert.equal(chip.textContent.trim(), '1 of 3 checks failed');
+      });
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-configure-link.html b/gr-checks/gr-checks-configure-link.html
new file mode 100644
index 0000000..23cbb6e
--- /dev/null
+++ b/gr-checks/gr-checks-configure-link.html
@@ -0,0 +1,18 @@
+<dom-module id="gr-checks-configure-link">
+  <template>
+    <style>
+      iron-icon {
+        height: 1.2rem;
+        margin-right: 4px;
+        width: 1.2rem;
+      }
+    </style>
+    <a href$="[[configurePath]]">
+      <gr-button link no-uppercase>
+        <iron-icon icon="gr-icons:settings"></iron-icon>
+        Configure checks
+      </gr-button>
+    </a>
+  </template>
+  <script src="gr-checks-configure-link.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-configure-link.js b/gr-checks/gr-checks-configure-link.js
new file mode 100644
index 0000000..0eb1571
--- /dev/null
+++ b/gr-checks/gr-checks-configure-link.js
@@ -0,0 +1,11 @@
+(function() {
+'use strict';
+
+Polymer({
+  is: 'gr-checks-configure-link',
+
+  properties: {
+    configurePath: String,
+  },
+});
+})();
diff --git a/gr-checks/gr-checks-configure-link_test.html b/gr-checks/gr-checks-configure-link_test.html
new file mode 100644
index 0000000..3c8f314
--- /dev/null
+++ b/gr-checks/gr-checks-configure-link_test.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-configure-link</title>
+<link rel="import" href="webcomponent_lib/gr-checks-configure-link.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-configure-link configure-path="[[configurePath]]"></gr-checks-configure-link>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-checks-configure-link tests', () => {
+    let element;
+    let sandbox;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+      element = fixture('basic', {
+        configurePath: 'http://example.com/test-configure',
+      });
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    test('renders the link to configure a check', () => {
+      const configureLink = element.$$('a');
+      assert.equal(configureLink.getAttribute('href'), 'http://example.com/test-configure');
+      assert.equal(configureLink.textContent.trim(), 'Configure checks');
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-item.html b/gr-checks/gr-checks-item.html
new file mode 100644
index 0000000..436f65b
--- /dev/null
+++ b/gr-checks/gr-checks-item.html
@@ -0,0 +1,46 @@
+<dom-module id="gr-checks-item">
+  <template>
+      <style>
+        :host {
+          border-top: 1px solid #ddd;
+        }
+
+        td:first-child {
+          padding-left: 1rem;
+        }
+
+        td {
+          padding: 1px 32px 1px 0;
+          white-space: nowrap;
+        }
+
+        a.log {
+          margin-right: 16px;
+          display: inline-block;
+        }
+      </style>
+
+      <td>[[_checkerDescription]]</td>
+      <td><!-- required for merge--></td>
+      <td>
+        <gr-checks-status show-text status="[[check.state]]"></gr-checks-status>
+      </td>
+      <td>Check System</td>
+      <td>[[_startTime]]</td>
+      <td>[[_duration]]</td>
+      <td>
+        <a href$="[[check.url]]" target="_blank" class="log">
+          <gr-button link no-uppercase>
+            View log
+          </gr-button>
+        </a>
+        <gr-button
+          link
+          no-uppercase
+          on-tap="handleClick">
+          Re-run
+        </gr-button>
+      </td>
+  </template>
+  <script src="gr-checks-item.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-item.js b/gr-checks/gr-checks-item.js
new file mode 100644
index 0000000..272f82c
--- /dev/null
+++ b/gr-checks/gr-checks-item.js
@@ -0,0 +1,120 @@
+(function() {
+  'use strict';
+
+  const Defs = {};
+  /**
+   * @typedef {{
+ *   id: string,
+ *   projectId: string,
+ *   checkerId: string,
+ *   startTime: string,
+ *   finishTime: string,
+ * }}
+   */
+  Defs.Check;
+
+  Polymer({
+    is: 'gr-checks-item',
+
+    properties: {
+      check: Object,
+      /** @type {function(string): !Promise<!Object>} */
+      getChecker: Function,
+      /** @type {function(string): !Promise<!Object>} */
+      retryCheck: Function,
+      _checkerDescription: String,
+      _startTime: {
+        type: String,
+        computed: '_computeStartTime(check)',
+      },
+      _duration: {
+        type: String,
+        computed: '_computeDuration(check)',
+      },
+    },
+
+    observers: [
+      '_updateCheckerName(check, getChecker)',
+    ],
+
+    /**
+     * @param {!Defs.Check} check
+     * @param {function(string): !Promise<!Object>} getChecker
+     */
+    _updateCheckerName(check, getChecker) {
+      const checkerId = check.checker_uuid;
+      getChecker(checkerId).then(
+          checker => checker && checker.description || checkerId,
+          () => checkerId).then(checkerDescription => {
+        this.set('_checkerDescription', checkerDescription);
+      });
+    },
+
+    /**
+     * @param {!Defs.Check} check
+     * @return {string}
+     */
+    _computeStartTime(check) {
+      return moment(check.created).format('l');
+    },
+
+    /**
+     * @param {!Defs.Check} check
+     * @return {string}
+     */
+    _computeDuration(check) {
+      const startTime = moment(check.created);
+      const finishTime = moment(check.updated);
+      return generateDurationString(
+          moment.duration(finishTime.diff(startTime)));
+    },
+
+    handleClick(event) {
+      event.preventDefault();
+      this.retryCheck(this.check.checker_uuid);
+    },
+  });
+
+  const ZERO_SECONDS = '0 sec';
+
+  /**
+   * @param {!Moment.Duration} duration a moment object
+   * @return {string}
+   */
+  function generateDurationString(duration) {
+    if (duration.asSeconds() === 0) {
+      return ZERO_SECONDS;
+    }
+
+    const durationSegments = [];
+    if (duration.seconds()) {
+      durationSegments.push(`${duration.seconds()} sec`);
+    }
+    if (duration.minutes()) {
+      durationSegments.push(`${duration.minutes()} min`);
+    }
+    if (duration.hours()) {
+      const hours = pluralize(duration.hours(), 'hour', 'hours');
+      durationSegments.push(`${duration.hours()} ${hours}`);
+    }
+    if (duration.days()) {
+      const days = pluralize(duration.days(), 'day', 'days');
+      durationSegments.push(`${duration.days()} ${days}`);
+    }
+    if (duration.months()) {
+      const months = pluralize(duration.months(), 'month', 'months');
+      durationSegments.push(`${duration.months()} ${months}`);
+    }
+    return durationSegments.join(' ');
+  }
+
+  /**
+   * @param {number} unit
+   * @param {string} singular
+   * @param {string} plural
+   * @return {string}
+   */
+  function pluralize(unit, singular, plural) {
+    return unit === 1 ? singular : plural;
+  }
+})();
diff --git a/gr-checks/gr-checks-item_test.html b/gr-checks/gr-checks-item_test.html
new file mode 100644
index 0000000..0495d52
--- /dev/null
+++ b/gr-checks/gr-checks-item_test.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-item</title>
+<link rel="import" href="webcomponent_lib/gr-checks-item.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-item
+      check="{{check}}"
+      get-checker="[[getChecker]]"
+      retry-check="[[retryCheck]]">
+    </gr-checks-item>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-checks-item tests', () => {
+    let element;
+    let sandbox;
+    let getCheckerSpy;
+    let resolveGetChecker;
+    let rejectGetChecker;
+    let retryCheckSpy;
+
+    setup((done) => {
+      sandbox = sinon.sandbox.create();
+      const getCheckerPromise = new Promise((resolve, reject) => {
+        resolveGetChecker = resolve;
+        rejectGetChecker = reject;
+      });
+      getCheckerSpy = sinon.stub();
+      getCheckerSpy.returns(getCheckerPromise);
+      retryCheckSpy = sinon.stub();
+      retryCheckSpy.returns(Promise.resolve());
+
+      element = fixture('basic', {
+        getChecker: getCheckerSpy,
+        retryCheck: retryCheckSpy,
+        check: {
+          checkId: 'test-check-id',
+          logUrl: 'http://example.com/test-log-url',
+          startTime: "2019-02-06T22:25:19.269Z",
+          finishTime: "2019-02-06T22:25:44.574Z",
+        },
+      });
+      flush(done);
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    suite('checker name', () => {
+      test('calls getChecker', () => {
+        assert.isTrue(getCheckerSpy.called);
+        assert.isTrue(getCheckerSpy.calledWith('test-check-id'));
+      });
+
+      function assertCheckerText(text) {
+        const name = element.$$('td:nth-child(1)');
+        assert.equal(name.textContent.trim(), text);
+      }
+
+      suite('on success', () => {
+        setup(done => {
+          resolveGetChecker({description: 'test checker name'});
+          flush(done);
+        });
+
+        test('renders the checker name on success', () => {
+          assertCheckerText('test checker name');
+        });
+      });
+
+      suite('on empty response', () => {
+        setup(done => {
+          resolveGetChecker();
+          flush(done);
+        });
+
+        test('renders the id when there is no description', () => {
+          assertCheckerText('test-check-id');
+        });
+      });
+
+      suite('on error', () => {
+        setup(done => {
+          rejectGetChecker(new Error('broken'));
+          flush(done);
+        });
+
+        test('renders the id when the call fails', () => {
+          assertCheckerText('test-check-id');
+        });
+      });
+    });
+
+    test('renders the status', () => {
+      const status = element.$$('td:nth-child(3) > gr-checks-status');
+      assert.exists(status);
+    });
+
+    test('renders the checking system', () => {
+      const checkingSystem = element.$$('td:nth-child(4)');
+      assert.equal(checkingSystem .textContent.trim(), "Check System");
+    });
+
+    test('renders the run date', () => {
+      const name = element.$$('td:nth-child(5)');
+      assert.equal(name .textContent.trim(), "2/6/2019");
+    });
+
+    suite('duration', () => {
+      test('renders the run duration', () => {
+        const name = element.$$('td:nth-child(6)');
+        assert.equal(name .textContent.trim(), "25 sec");
+      });
+
+      test('renders 0 sec when the start and end time are the same', () => {
+        element.check = {
+          checkId: 'test-check-id',
+          logUrl: 'http://example.com/test-log-url',
+          startTime: "2019-02-06T22:25:19.269Z",
+          finishTime: "2019-02-06T22:25:19.269Z",
+        };
+        const name = element.$$('td:nth-child(6)');
+        assert.equal(name .textContent.trim(), "0 sec");
+      });
+    });
+
+    test('renders a link to the log', () => {
+      const logLink = element.$$('td:nth-child(7) > a');
+      assert.equal(logLink.getAttribute('href'), "http://example.com/test-log-url");
+      assert.equal(logLink.textContent.trim(), "View log");
+    });
+
+    suite('retryCheck', () => {
+      let retryCheckLink;
+
+      setup(() => {
+        retryCheckLink = element.$$('td:nth-child(7) > gr-button');
+      });
+
+      test('shows a link to the log url', () => {
+        assert.equal(retryCheckLink.textContent.trim(), "Re-run");
+      });
+
+      test('clicking on the link calls the retryCheck property', () => {
+        assert.isFalse(retryCheckSpy.called);
+        retryCheckLink.click();
+        assert.isTrue(retryCheckSpy.called);
+      });
+    });
+  });
+</script>
diff --git a/gr-checks/gr-checks-status.html b/gr-checks/gr-checks-status.html
new file mode 100644
index 0000000..ef4a02a
--- /dev/null
+++ b/gr-checks/gr-checks-status.html
@@ -0,0 +1,67 @@
+<dom-module id="gr-checks-status">
+  <template>
+      <style>
+        :host {
+          display: inline-block;
+        }
+        i {
+          border-radius: 50%;
+          color: white;
+          display: inline-block;
+          font-style: normal;
+          height: 16px;
+          margin-right: 4px;
+          text-align: center;
+          width: 16px;
+        }
+        .successful > i {
+          background-color: #00C752;
+        }
+        .failed > i {
+          background-color: #DA4236;
+        }
+        .in-progress > i {
+          background-color: #ddd;
+        }
+        .unevaluated > i {
+          background-color: black;
+        }
+      </style>
+    <span class$="[[_className]]">
+      <template is="dom-if" if="[[_isUnevaluated(status)]]">
+        <i>⏹</i>
+        <template is="dom-if" if="[[showText]]">
+          <span>
+            Unevaluated
+          </span>
+        </template>
+      </template>
+      <template is="dom-if" if="[[_isInProgress(status)]]">
+        <i>…</i>
+        <template is="dom-if" if="[[showText]]">
+          <span>
+            In progress
+          </span>
+        </template>
+      </template>
+      <template is="dom-if" if="[[_isSuccessful(status)]]">
+        <i>✓</i>
+        <template is="dom-if" if="[[showText]]">
+          <span>
+            Successful
+          </span>
+        </template>
+      </template>
+      <template is="dom-if" if="[[_isFailed(status)]]">
+        <i>!</i>
+        <template is="dom-if" if="[[showText]]">
+          <span>
+            Failed
+          </span>
+        </template>
+      </template>
+    </span>
+  </template>
+  <script src="gr-checks-all-statuses.js"></script>
+  <script src="gr-checks-status.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-status.js b/gr-checks/gr-checks-status.js
new file mode 100644
index 0000000..e187b20
--- /dev/null
+++ b/gr-checks/gr-checks-status.js
@@ -0,0 +1,37 @@
+(function() {
+'use strict';
+
+Polymer({
+  is: 'gr-checks-status',
+
+  properties: {
+    showText: {
+      type: Boolean,
+      value: false,
+      reflectToAttribute: true,
+    },
+    status: String,
+    _className: {type: String, computed: '_computeClassName(status)'},
+  },
+
+  _isUnevaluated(status) {
+    return window.Gerrit.Checks.isUnevaluated(status);
+  },
+
+  _isInProgress(status) {
+    return window.Gerrit.Checks.isInProgress(status);
+  },
+
+  _isSuccessful(status) {
+    return window.Gerrit.Checks.isSuccessful(status);
+  },
+
+  _isFailed(status) {
+    return window.Gerrit.Checks.isFailed(status);
+  },
+
+  _computeClassName(status) {
+    return window.Gerrit.Checks.statusClass(status);
+  },
+});
+})();
diff --git a/gr-checks/gr-checks-status_test.html b/gr-checks/gr-checks-status_test.html
new file mode 100644
index 0000000..19957a3
--- /dev/null
+++ b/gr-checks/gr-checks-status_test.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-item</title>
+<link rel="import" href="webcomponent_lib/gr-checks-status.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-status show-text="{{showText}}" status="{{status}}"></gr-checks-status>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-checks-status tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+    });
+
+    teardown(() => { sandbox.restore(); });
+
+    function testStatus(expectedStatusClass, statusText, statusesToTest) {
+      suite(expectedStatusClass, () => {
+
+        for(let statusToTest of statusesToTest) {
+          test(`renders ${expectedStatusClass} for ${statusToTest}`, (done) => {
+            const element = fixture('basic', {
+              status: statusToTest,
+              showText: false,
+            });
+
+            flush(() => {
+              const icon = element.$$(`span.${expectedStatusClass} i`);
+              assert.exists(icon);
+              done();
+            });
+          });
+          test(`renders ${expectedStatusClass} for ${statusToTest} rendering text ${statusText}`, (done) => {
+            const element = fixture('basic', {
+              status: statusToTest,
+              showText: true,
+            });
+
+            flush(() => {
+              const text = element.$$(`span.${expectedStatusClass} span`);
+              assert.equal(text.textContent.trim(), statusText);
+              done();
+            });
+          });
+        }
+      });
+    }
+
+    testStatus('successful', 'Successful', ['SUCCESS']);
+    testStatus('failed', 'Failed', ['FAILURE', 'INTERNAL_ERROR', 'TIMEOUT']);
+    testStatus('in-progress', 'In progress', ['QUEUING', 'QUEUED', 'WORKING']);
+    testStatus('unevaluated', 'Unevaluated', ['STATUS_UNKNOWN', 'CANCELLED']);
+  });
+</script>
diff --git a/gr-checks/gr-checks-view.html b/gr-checks/gr-checks-view.html
new file mode 100644
index 0000000..5f1b94e
--- /dev/null
+++ b/gr-checks/gr-checks-view.html
@@ -0,0 +1,116 @@
+<dom-module id="gr-checks-view">
+  <template>
+    <style>
+      :host {
+        display: block;
+        width: 100%;
+      }
+
+      table {
+        width: 100%;
+      }
+
+      gr-checks-item {
+        display: table-row;
+      }
+
+      .headerRow {
+        border-bottom: 1px solid #ddd;
+      }
+
+      .topHeader {
+        padding-bottom: 4px;
+        text-align: left;
+        white-space: nowrap;
+      }
+
+      th.topHeader:last-child {
+        width: 100%;
+      }
+
+      h2 {
+        font-size: 1.17em;
+        font-weight: 500;
+      }
+
+      h3 {
+        flex: 1;
+        font-size: 1.17em;
+        font-weight: 500;
+        margin-bottom: 1.5rem;
+      }
+
+      header {
+        display: flex;
+        margin: 1rem 1rem 0;
+      }
+
+      table {
+        margin-bottom: 16px;
+      }
+
+      th:first-child {
+        padding-left: 1rem;
+      }
+
+      .no-content {
+        min-height: 106px;
+        padding: 24px 0;
+        text-align: center;
+      }
+    </style>
+
+    <template is="dom-if" if="[[_isLoading(_status)]]">
+      <div class="no-content">
+        <p>Loading...</p>
+      </div>
+    </template>
+
+    <template is="dom-if" if="[[_isEmpty(_status)]]">
+      <div class="no-content">
+        <h2>No checks ran for this code review</h2>
+        <p>Configure checkers to view the results here.</p>
+        <gr-checks-configure-link configure-path="[[configurePath]]">
+        </gr-checks-configure-link>
+      </div>
+    </template>
+
+    <template is="dom-if" if="[[_isNotConfigured(_status)]]">
+      <div class="no-content">
+        <h2>Code review checks not configured</h2>
+        <p>Configure checkers to view the results here.</p>
+        <gr-checks-configure-link configure-path="[[configurePath]]">
+        </gr-checks-configure-link>
+      </div>
+    </template>
+
+    <template is="dom-if" if="[[_hasResults(_status)]]">
+      <header>
+        <h3>Latest checks for Patchset [[revision._number]]</h3>
+        <gr-checks-configure-link configure-path="[[configurePath]]">
+        </gr-checks-configure-link>
+      </header>
+
+      <table>
+        <thead>
+          <tr class="headerRow">
+            <th class="topHeader">Name</th>
+            <th class="topHeader"><!-- required for merge --></th>
+            <th class="topHeader">Status</th>
+            <th class="topHeader">Checking system</th>
+            <th class="topHeader">Started</th>
+            <th class="topHeader">Duration</th>
+            <th class="topHeader"><!-- actions --></th>
+          </tr>
+        </thead>
+        <tbody>
+          <template is="dom-repeat" items="[[_checks]]" as="check">
+            <gr-checks-item check="[[check]]" retry-check="[[retryCheck]]" get-checker="[[getChecker]]"></gr-checks-item>
+          </template>
+        </tbody>
+      </table>
+    </template>
+  </template>
+
+  <script src="gr-checks-view.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checks-view.js b/gr-checks/gr-checks-view.js
new file mode 100644
index 0000000..3f53f61
--- /dev/null
+++ b/gr-checks/gr-checks-view.js
@@ -0,0 +1,103 @@
+(function() {
+'use strict';
+
+const Defs = {};
+/**
+ * @typedef {{
+ *   revisions: !Object<string, !Object>,
+ * }}
+ */
+Defs.Change;
+
+/**
+ * @param {!Defs.Change} change The current CL.
+ * @param {!Object} revision The current patchset.
+ * @return {string|undefined}
+ */
+function currentRevisionSha(change, revision) {
+  return Object.keys(change.revisions)
+      .find(sha => change.revisions[sha] === revision);
+}
+
+const LoadingStatus = {
+  LOADING: 0,
+  EMPTY: 1,
+  RESULTS: 2,
+  NOT_CONFIGURED: 3,
+};
+
+Polymer({
+  is: 'gr-checks-view',
+
+  properties: {
+    revision: Object,
+    change: Object,
+    // TODO(brohlfs): Implement getChecks based on Checks Rest API.
+    /** @type {function(string, (string|undefined)): !Promise<!Object>} */
+    getChecks: Function,
+    // TODO(brohlfs): Implement isConfigured based on Checks Rest API.
+    /** @type {function(string): !Promise<!Object>} */
+    isConfigured: Function,
+    // TODO(brohlfs): Implement getChecker based on Checks Rest API.
+    /** @type {function(string, string): !Promise<!Object>} */
+    getChecker: Function,
+    // TODO(brohlfs): Implement retryCheck based on Checks Rest API.
+    /** @type {function(string, string): !Promise<!Object>} */
+    retryCheck: Function,
+    // TODO(brohlfs): Implement configurePath based on Checks Rest API.
+    // The url path to configure checkers.
+    configurePath: String,
+    _checks: Object,
+    _status: {
+      type: Object,
+      value: LoadingStatus.LOADING,
+    },
+  },
+
+  observers: [
+    '_fetchChecks(change, revision, getChecks)',
+  ],
+
+  /**
+   * @param {!Defs.Change} change The current CL.
+   * @param {!Object} revision The current patchset.
+   * @param {function(string, (string|undefined)): !Promise<!Object>}
+   *     getChecks function to get checks.
+   */
+  _fetchChecks(change, revision, getChecks) {
+    const repository = change['project'];
+    const gitSha = currentRevisionSha(change, revision);
+
+    getChecks(repository, gitSha).then(checks => {
+      if (checks && checks.length) {
+        this.set('_checks', checks);
+        this.set('_status', LoadingStatus.RESULTS);
+      } else {
+        this._checkConfigured();
+      }
+    });
+  },
+
+  _checkConfigured() {
+    const repository = this.change['project'];
+    this.isConfigured(repository).then(configured => {
+      const status =
+          configured ? LoadingStatus.EMPTY : LoadingStatus.NOT_CONFIGURED;
+      this.set('_status', status);
+    });
+  },
+
+  _isLoading(status) {
+    return status === LoadingStatus.LOADING;
+  },
+  _isEmpty(status) {
+    return status === LoadingStatus.EMPTY;
+  },
+  _hasResults(status) {
+    return status === LoadingStatus.RESULTS;
+  },
+  _isNotConfigured(status) {
+    return status === LoadingStatus.NOT_CONFIGURED;
+  },
+});
+})();
diff --git a/gr-checks/gr-checks-view_test.html b/gr-checks/gr-checks-view_test.html
new file mode 100644
index 0000000..6affa9c
--- /dev/null
+++ b/gr-checks/gr-checks-view_test.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<script src="imports.js"></script>
+<script src="webcomponentsjs/webcomponents-lite.js"></script>
+<link rel="import" href="polymer/polymer.html">
+
+<title>gr-checks-item</title>
+<link rel="import" href="webcomponent_lib/gr-checks-view.html">
+
+<test-fixture id="basic">
+  <template is="dom-template">
+    <gr-checks-view
+      change="[[change]]"
+      revision="[[revision]]"
+      configure-path="[[configurePath]]"
+      get-checks="[[getChecks]]"
+      is-configured="[[isConfigured]]"
+      get-checker="[[getChecker]]"
+      retry-check="[[retryCheck]]">
+    </gr-checks-view>
+  </template>
+</test-fixture>
+
+<script>
+  const CHECK1 = {
+    checkId: 'test-check-id',
+    logUrl: 'http://example.com/test-log-url',
+    startTime: "2019-02-06T22:25:19.269Z",
+    finishTime: "2019-02-06T22:25:44.574Z",
+  };
+const REVISION = {
+  "kind": "REWORK",
+  "_number": 3,
+  "created": "2018-05-15 21:56:13.000000000",
+  "uploader": {
+    "_account_id": 1000000,
+  },
+  "ref": "refs/changes/00/1000/1",
+  "commit": {
+    "parents": [],
+    "subject": "added test file",
+    "message": "added test file\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n",
+    "commit": "1c9a1dfd38ea51dc7880f3ddf669100710f0c91b"
+  },
+};
+
+suite('gr-checks-view tests', () => {
+  let element;
+  let sandbox;
+  let getChecksSpy;
+  let getChecksResolve;
+  let getCheckerSpy;
+  let retryCheckSpy;
+  let isConfiguredSpy;
+  let isConfiguredResolve;
+
+  setup((done) => {
+    sandbox = sinon.sandbox.create();
+
+    getChecksSpy = sinon.stub();
+    const getChecksPromise = new Promise((resolve, reject) => {
+      getChecksResolve = resolve;
+    });
+    getChecksSpy.returns(getChecksPromise);
+
+    isConfiguredSpy = sinon.stub();
+    const isConfiguredPromise = new Promise((resolve, reject) => {
+      isConfiguredResolve = resolve;
+    });
+    isConfiguredSpy.returns(isConfiguredPromise);
+
+    getCheckerSpy = sinon.stub();
+    getCheckerSpy.returns(Promise.resolve({description: 'test checker name'}));
+    retryCheckSpy = sinon.stub();
+    retryCheckSpy.returns(Promise.resolve());
+
+    element = fixture('basic', {
+      getChecker: getCheckerSpy,
+      retryCheck: retryCheckSpy,
+      getChecks: getChecksSpy,
+      isConfigured: isConfiguredSpy,
+      configurePath: 'http://example.com/test-configure',
+      change: {
+        'project': 'test-repository',
+        'revisions': {
+          'first-sha': "test-revision",
+          'second-sha': REVISION,
+        }
+      },
+      revision: REVISION,
+    });
+    flush(done);
+  });
+
+  teardown(() => { sandbox.restore(); });
+
+  test('renders loading', () => {
+    assert.equal(element.textContent.trim(), 'Loading...');
+  });
+
+  test('queries the checks', () => {
+    assert.isTrue(getChecksSpy.called);
+    assert.isTrue(getChecksSpy.calledWith('test-repository', 'second-sha'));
+  });
+
+  suite('no checks returned', () => {
+    setup((done) => {
+      getChecksResolve([]);
+      flush(done);
+    });
+
+    test('it calls to check if the checks are configured', () => {
+      assert.isTrue(isConfiguredSpy.called);
+      assert.isTrue(isConfiguredSpy.calledWith('test-repository'));
+    });
+
+    suite('not configured', () => {
+      setup((done) => {
+        isConfiguredResolve(false);
+        flush(done);
+      });
+
+      test('renders checks not configured', () => {
+        const header = element.$$('h2');
+        assert.equal(header.textContent.trim(), 'Code review checks not configured');
+      });
+
+      test('renders the link to configure a check', () => {
+        const configureLink = element.$$('gr-checks-configure-link');
+        assert.exists(configureLink);
+      });
+    });
+
+    suite('no checks ran', () => {
+      setup((done) => {
+        isConfiguredResolve(true);
+        flush(done);
+      });
+
+      test('renders checks not configured', () => {
+        const header = element.$$('h2');
+        assert.equal(header.textContent.trim(), 'No checks ran for this code review');
+      });
+
+      test('renders the link to configure a check', () => {
+        const configureLink = element.$$('gr-checks-configure-link');
+        assert.exists(configureLink);
+      });
+    });
+  });
+
+  suite('with checks', () => {
+    setup(done => {
+      getChecksResolve([CHECK1, CHECK1, CHECK1]);
+      flush(done);
+    });
+
+    test('it calls to check if the checks are configured', () => {
+      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 the link to configure a check', () => {
+      const configureLink = element.$$('header > gr-checks-configure-link');
+      assert.exists(configureLink);
+    });
+
+    test('renders a table of all the checks', () => {
+      const tbody = element.$$('table > tbody');
+      assert.lengthOf(tbody.querySelectorAll('gr-checks-item'), 3)
+    });
+  });
+});
+</script>
diff --git a/gr-checks/gr-checks.html b/gr-checks/gr-checks.html
new file mode 100644
index 0000000..4efdf97
--- /dev/null
+++ b/gr-checks/gr-checks.html
@@ -0,0 +1,28 @@
+<link rel="import" href="gr-checks-view.html">
+<link rel="import" href="gr-checks-item.html">
+<link rel="import" href="gr-checks-status.html">
+<link rel="import" href="gr-checks-chip-view.html">
+<link rel="import" href="gr-checks-change-list-item-cell-view.html">
+<link rel="import" href="gr-checks-change-list-header-view.html">
+<link rel="import" href="gr-checks-change-view-tab-header-view.html">
+<link rel="import" href="gr-checks-configure-link.html">
+
+<dom-module id="gr-checks">
+  <script>
+    Gerrit.install(plugin => {
+      plugin.registerDynamicCustomComponent(
+          'change-list-header',
+          'gr-checks-change-list-header-view');
+      plugin.registerDynamicCustomComponent(
+          'change-list-item-cell',
+          'gr-checks-change-list-item-cell-view');
+      plugin.registerDynamicCustomComponent(
+          'change-view-tab-header',
+          'gr-checks-change-view-tab-header-view');
+      plugin.registerDynamicCustomComponent(
+          'change-view-tab-content',
+          'gr-checks-view').onAttached(
+          view => console.log('gr-checks-view attached'));
+    });
+  </script>
+</dom-module>
diff --git a/java/com/google/gerrit/plugins/checks/api/CheckInput.java b/java/com/google/gerrit/plugins/checks/api/CheckInput.java
index b337a79..633d2fb 100644
--- a/java/com/google/gerrit/plugins/checks/api/CheckInput.java
+++ b/java/com/google/gerrit/plugins/checks/api/CheckInput.java
@@ -34,10 +34,10 @@
 
   @Override
   public boolean equals(Object o) {
-    if (!(o instanceof CheckInfo)) {
+    if (!(o instanceof CheckInput)) {
       return false;
     }
-    CheckInfo other = (CheckInfo) o;
+    CheckInput other = (CheckInput) o;
     return Objects.equals(other.checkerUuid, checkerUuid)
         && Objects.equals(other.state, state)
         && Objects.equals(other.url, url)