Merge "Add UI for adding and editing checkers"
diff --git a/gr-checks/gr-checkers-list.html b/gr-checks/gr-checkers-list.html
new file mode 100644
index 0000000..36e8f85
--- /dev/null
+++ b/gr-checks/gr-checkers-list.html
@@ -0,0 +1,163 @@
+<link rel="import" href="gr-create-checkers-dialog.html">
+
+<!-- Expects core to import these functionalities
+     gr-list-view/gr-list-view.html
+     styles/gr-form-styles.html
+     /gr-icons/gr-icons.html
+     /iron-input/iron-input.html -->
+
+<dom-module id="gr-checkers-list">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-table-styles"></style>
+    <style>
+      iron-icon {
+        cursor: pointer;
+      }
+      #filter {
+        font-size: var(--font-size-normal);
+        max-width: 25em;
+      }
+      #filter:focus {
+        outline: none;
+      }
+      #topContainer {
+        align-items: center;
+        display: flex;
+        height: 3rem;
+        justify-content: space-between;
+        margin: 0 1em;
+      }
+      #createNewContainer:not(.show) {
+        display: none;
+      }
+      a {
+        color: var(--primary-text-color);
+        text-decoration: none;
+      }
+      nav {
+        align-items: center;
+        display: flex;
+        height: 3rem;
+        justify-content: flex-end;
+        margin-right: 20px;
+      }
+      nav,
+      iron-icon {
+        color: var(--deemphasized-text-color);
+      }
+      .nav-iron-icon {
+        height: 1.85rem;
+        margin-left: 16px;
+        width: 1.85rem;
+      }
+      .nav-buttons:hover {
+        text-decoration: underline;
+        cursor: pointer;
+      }
+    </style>
+
+
+    <div id="topContainer">
+      <div>
+        <label>Filter:</label>
+        <iron-input
+            type="text"
+            bind-value="{{_filter}}">
+          <input
+              is="iron-input"
+              type="text"
+              id="filter"
+              bind-value="{{_filter}}">
+        </iron-input>
+      </div>
+      <div id="createNewContainer"
+          class$="[[_computeCreateClass(_createNewCapability)]]">
+        <gr-button primary link id="createNew" on-tap="_handleCreateClicked">
+          Create New
+        </gr-button>
+      </div>
+    </div>
+
+    <table id="list" class="genericList">
+      <tr class="headerRow">
+        <th class="name topHeader">Checker Name</th>
+        <th class="name topHeader">Repository</th>
+        <th class="name topHeader">Status</th>
+        <th class="name topHeader">Required</th>
+        <th class="topHeader description">Checker Description</th>
+        <th class="name topHeader"> Edit </th>
+      </tr>
+      <tbody class$="[[computeLoadingClass(_loading)]]">
+        <template is="dom-repeat" items="[[_visibleCheckers]]">
+          <tr class="table">
+            <td class="name">
+              <a>[[item.name]]</a>
+            </td>
+            <td class="name">[[item.repository]]</td>
+            <td class="name">[[item.status]]</td>
+            <td class="name">[[_computeBlocking(item)]]</td>
+            <td class="description">[[item.description]]</td>
+            <td on-tap="_handleEditIconClicked">
+              <iron-icon icon="gr-icons:edit"></iron-icon>
+            </td>
+          </tr>
+        </template>
+      </tbody>
+    </table>
+
+    <nav>
+      <template is="dom-if" if="[[_showPrevButton]]">
+        <a class="nav-buttons" id="prevArrow"
+          on-tap="_handlePrevClicked">
+          <iron-icon class="nav-iron-icon" icon="gr-icons:chevron-left"></iron-icon>
+        </a>
+      </template>
+      <template is="dom-if" if="[[_showNextButton]]">
+        <a class="nav-buttons" id="nextArrow"
+          on-tap="_handleNextClicked">
+          <iron-icon icon="gr-icons:chevron-right"></iron-icon>
+        </a>
+      </template>
+    </nav>
+
+    <gr-overlay id="createOverlay" with-backdrop>
+      <gr-dialog
+          id="createDialog"
+          confirm-label="Create"
+          on-confirm="_handleCreateConfirm"
+          on-cancel="_handleCreateCancel">
+        <div class="header" slot="header">
+          Create Checkers
+        </div>
+        <div slot="main">
+          <gr-create-checkers-dialog
+            id="createNewModal"
+            plugin-rest-api="[[pluginRestApi]]">
+          </gr-create-checkers-dialog>
+        </div>
+      </gr-dialog>
+    </gr-overlay>
+    <gr-overlay id="editOverlay" with-backdrop>
+      <gr-dialog
+          id="editDialog"
+          confirm-label="Save"
+          on-confirm="_handleEditConfirm"
+          on-cancel="_handleEditCancel">
+        <div class="header" slot="header">
+          Edit Checker
+        </div>
+        <div slot="main">
+          <gr-create-checkers-dialog
+              checker="[[checker]]"
+              plugin-rest-api="[[pluginRestApi]]"
+              on-cancel="_handleEditCancel"
+              id="editModal">
+          </gr-create-checkers-dialog>
+        </div>
+      </gr-dialog>
+    </gr-overlay>
+      <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-checkers-list.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-checkers-list.js b/gr-checks/gr-checkers-list.js
new file mode 100644
index 0000000..0967c03
--- /dev/null
+++ b/gr-checks/gr-checkers-list.js
@@ -0,0 +1,174 @@
+(function() {
+  'use strict';
+  const CHECKERS_PER_PAGE = 15;
+  const GET_CHECKERS_URL = "/a/plugins/checks/checkers/";
+
+  /**
+   * Show a list of all checkers along with creating/editing them
+   */
+  Polymer({
+    is: 'gr-checkers-list',
+    properties: {
+      pluginRestApi: Object,
+      // Checker that will be passed to the editOverlay modal
+      checker: Object,
+      _checkers: Array,
+      // List of checkers that contain the filtered text
+      _filteredCheckers: Array,
+      _loading: {
+        type: Boolean,
+        value: true,
+      },
+      _filter: {
+        type: String,
+        value: '',
+      },
+      _visibleCheckers: {
+        type: Array,
+        computed: '_computeVisibleCheckers(_startingIndex, _filteredCheckers)',
+        observer: '_visibleCheckersChanged'
+      },
+      _createNewCapability: {
+        type: Boolean,
+        value: true,
+      },
+      _startingIndex: {
+        type: Number,
+        value: 0
+      },
+      _showNextButton: {
+        type: Boolean,
+        value: true,
+        computed: '_computeShowNextButton(_startingIndex, _filteredCheckers)'
+      },
+      _showPrevButton: {
+        type: Boolean,
+        value: true,
+        computed: '_computeShowPrevButton(_startingIndex, _filteredCheckers)'
+      },
+    },
+    observers: [
+      '_showCheckers(_checkers, _filter)',
+    ],
+
+    attached() {
+      this._getCheckers();
+    },
+
+    _contains(target, keyword) {
+      return target.toLowerCase().includes(keyword.toLowerCase().trim());
+    },
+
+    _visibleCheckersChanged(currentVisibleCheckers, previousVisibleCheckers) {
+      if (!currentVisibleCheckers || !previousVisibleCheckers) {
+        return;
+      }
+      if (currentVisibleCheckers.length !== previousVisibleCheckers.length) {
+        this.fire('resize', {bubbles: false});
+      }
+    },
+
+    _showCheckers(_checkers, _filter) {
+      if (!_checkers) return;
+      if (!_filter) _filter = '';
+      // TODO(dhruvsri): highlight matching part
+      this._filteredCheckers = this._checkers.filter(checker =>
+        this._contains(checker.name, this._filter) ||
+        this._contains(checker.repository, this._filter))
+      this._startingIndex = 0;
+    },
+
+    computeLoadingClass(loading) {
+      return loading ? 'loading' : '';
+    },
+
+    _computeVisibleCheckers(_startingIndex, _filteredCheckers) {
+      if (!_filteredCheckers) {
+        return [];
+      }
+      return this._filteredCheckers.slice(this._startingIndex,
+        this._startingIndex + CHECKERS_PER_PAGE);
+    },
+
+    _computeShowNextButton(_startingIndex, _filteredCheckers) {
+      if (!_filteredCheckers) {
+        return false;
+      }
+      return _startingIndex + CHECKERS_PER_PAGE < _filteredCheckers.length;
+    },
+
+    _computeShowPrevButton(_startingIndex, _filteredCheckers) {
+      if (!_filteredCheckers) {
+        return false;
+      }
+      return _startingIndex >= CHECKERS_PER_PAGE;
+    },
+
+    _handleNextClicked() {
+      if (this._startingIndex + CHECKERS_PER_PAGE <
+        this._filteredCheckers.length) {
+          this._startingIndex += CHECKERS_PER_PAGE;
+      }
+    },
+
+    _handlePrevClicked() {
+      if (this._startingIndex >= CHECKERS_PER_PAGE) {
+        this._startingIndex -= CHECKERS_PER_PAGE;
+      }
+    },
+
+    _getCheckers() {
+      this.pluginRestApi.fetchJSON({
+        method: 'GET',
+        url: GET_CHECKERS_URL,
+      }).then(checkers => {
+        if (!checkers) { return; }
+        this._checkers = checkers;
+        this._startingIndex = 0;
+        this._loading = false;
+      });
+    },
+
+    _handleEditConfirm() {
+      this.$.editModal.handleEditChecker();
+    },
+
+    _handleEditIconClicked(e) {
+      let checker = e.model.item;
+      this.checker = checker;
+      this.$.editOverlay.open();
+    },
+
+    _handleEditCancel(e) {
+      if (e.detail.reload) {
+        this._getCheckers();
+      }
+      this.$.editOverlay.close();
+    },
+
+    _computeCreateClass(createNew) {
+      return createNew ? 'show' : '';
+    },
+
+    _computeBlocking(checker) {
+      return (checker && checker.blocking && checker.blocking.length > 0)
+        ? "YES": "NO";
+    },
+
+    _handleCreateConfirm() {
+      this.$.createNewModal.handleCreateChecker();
+    },
+
+    _handleCreateClicked() {
+      this.$.createOverlay.open();
+    },
+
+    _handleCreateCancel(e) {
+      if (e.detail.reload) {
+        this._getCheckers();
+      }
+      this.$.createOverlay.close();
+    },
+
+  })
+})();
\ No newline at end of file
diff --git a/gr-checks/gr-checks-view.html b/gr-checks/gr-checks-view.html
index 8a771c3..ee22cbb 100644
--- a/gr-checks/gr-checks-view.html
+++ b/gr-checks/gr-checks-view.html
@@ -1,4 +1,6 @@
 <link rel="import" href="gr-checks-status.html">
+<link rel="import" href="gr-checkers-list.html">
+
 <dom-module id="gr-checks-view">
   <template>
     <style>
@@ -84,8 +86,20 @@
         padding-left: 1em;
       }
 
+      .configure-button {
+        float:right;
+        margin-top: 10px;
+      }
+
+      #listOverlay {
+        width: 75%;
+      }
     </style>
 
+    <template is="dom-if" if="[[_createCheckerCapability]]">
+      <gr-button class="configure-button" on-tap="_handleConfigureClicked"> Configure </gr-button>
+    </template>
+
     <template is="dom-if" if="[[_isLoading(_status)]]">
       <div class="no-content">
         <p>Loading...</p>
@@ -150,7 +164,11 @@
         </tbody>
       </table>
     </template>
-  </template>
 
+    <gr-overlay id="listOverlay" with-backdrop>
+      <gr-checkers-list on-resize="_handleCheckersListResize" plugin-rest-api="[[pluginRestApi]]"></gr-checkers-list>
+    </gr-overlay>
+
+  </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
index bf28841..c312163 100644
--- a/gr-checks/gr-checks-view.js
+++ b/gr-checks/gr-checks-view.js
@@ -48,27 +48,60 @@
       isConfigured: Function,
       /** @type {function(string, string): !Promise<!Object>} */
       retryCheck: Function,
+      pluginRestApi: Object,
       _checks: Object,
       _status: {
         type: Object,
         value: LoadingStatus.LOADING,
       },
-      pollChecksInterval: Object,
+      pollChecksInterval: Number,
       visibilityChangeListenerAdded: {
         type: Boolean,
         value: false
-      }
+      },
+      _createCheckerCapability: {
+        type: Boolean,
+        value: false
+      },
     },
 
     observers: [
       '_pollChecksRegularly(change, revision, getChecks)',
     ],
 
+    attached() {
+      this.pluginRestApi = this.plugin.restApi();
+      this._initCreateCheckerCapability();
+    },
+
     detached() {
       clearInterval(this.pollChecksInterval);
       this.unlisten(document, 'visibilitychange', '_onVisibililityChange');
     },
 
+    _handleCheckersListResize() {
+      // Force polymer to recalculate position of overlay when length of
+      // checkers changes
+      this.$.listOverlay.refit();
+    },
+
+    _initCreateCheckerCapability() {
+      return this.pluginRestApi.getAccount().then(account => {
+        if (!account) { return; }
+        return this.pluginRestApi
+          .getAccountCapabilities(['checks-administrateCheckers'])
+          .then(capabilities => {
+            if (capabilities['checks-administrateCheckers']) {
+              this._createCheckerCapability = true;
+            }
+          });
+      });
+    },
+
+    _handleConfigureClicked() {
+      this.$.listOverlay.open();
+    },
+
     _orderChecks(a, b) {
       if (a.state != b.state) {
         let indexA = StatusPriorityOrder.indexOf(a.state);
diff --git a/gr-checks/gr-checks.html b/gr-checks/gr-checks.html
index cc4f6ca..2108121 100644
--- a/gr-checks/gr-checks.html
+++ b/gr-checks/gr-checks.html
@@ -12,7 +12,7 @@
 
       const getChecks = (change, revision) => {
         return plugin.restApi().get(
-            '/changes/' + change + '/revisions/' + revision + '/checks?o=CHECKER');
+          '/changes/' + change + '/revisions/' + revision + '/checks?o=CHECKER');
       };
 
       // TODO(brohlfs): Enable this dashboard column when search queries start
@@ -24,23 +24,25 @@
       //     'change-list-item-cell',
       //     'gr-checks-change-list-item-cell-view');
       plugin.registerCustomComponent(
-          'commit-container',
-          'gr-checks-chip-view').onAttached(
-          view => {
-            view['getChecks'] = getChecks;
-          });
+        'commit-container',
+        'gr-checks-chip-view').onAttached(
+        view => {
+          view['getChecks'] = getChecks;
+        }
+      );
       plugin.registerDynamicCustomComponent(
-          'change-view-tab-header',
-          'gr-checks-change-view-tab-header-view');
+        'change-view-tab-header',
+        'gr-checks-change-view-tab-header-view'
+      );
       plugin.registerDynamicCustomComponent(
-          'change-view-tab-content',
-          'gr-checks-view').onAttached(
-          view => {
-            view['isConfigured'] = (repository) => Promise.resolve(true);
-            // TODO(brohlfs): Implement retry.
-            view['retryCheck'] = (buildId) => undefined;
-            view['getChecks'] = getChecks;
-          });
-    });
+        'change-view-tab-content',
+        'gr-checks-view').onAttached(
+        view => {
+          view['isConfigured'] = (repository) => Promise.resolve(true);
+          // TODO(brohlfs): Implement retry.
+          view['retryCheck'] = (buildId) => undefined;
+          view['getChecks'] = getChecks;
+        });
+      });
   </script>
 </dom-module>
diff --git a/gr-checks/gr-create-checkers-dialog.html b/gr-checks/gr-create-checkers-dialog.html
new file mode 100644
index 0000000..e9aad47
--- /dev/null
+++ b/gr-checks/gr-create-checkers-dialog.html
@@ -0,0 +1,157 @@
+<link rel="import" href="gr-repo-chip.html">
+<dom-module id="gr-create-checkers-dialog">
+  <template>
+    <style include="gr-form-styles">
+      :host {
+        display: inline-block;
+      }
+      input {
+        width: 20em;
+      }
+      gr-autocomplete {
+        border: none;
+        --gr-autocomplete: {
+          border: 1px solid var(--border-color);
+          border-radius: 2px;
+          font-size: var(--font-size-normal);
+          height: 2em;
+          padding: 0 .15em;
+          width: 20em;
+        }
+      }
+      .error {
+        color: red;
+      }
+      #checkerSchemaInput[disabled] {
+        background-color: var(--table-subheader-background-color);
+      }
+      #checkerIdInput[disabled] {
+        background-color: var(--table-subheader-background-color);
+      }
+    </style>
+
+    <div class="gr-form-styles">
+      <div id="form">
+        <section hidden$="[[_errorMsg.length > 0]]">
+          <span class="error"> {{_errorMsg}} </span>
+        </section>
+        <section>
+          <span class="title">Name*</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_name}}">
+            <input is="iron-input"
+                   id="checkerNameInput"
+                   autocomplete="on"
+                   bind-value="{{_name}}">
+          </iron-input>
+        </section>
+        <section>
+          <span class="title">Description</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_description}}">
+            <input is="iron-input"
+                   id="checkerDescriptionInput"
+                   autocomplete="on"
+                   bind-value="{{_description}}">
+          </iron-input>
+        </section>
+        <section>
+          <span class="title">Repository*</span>
+          <div class="list">
+            <template id="chips" is="dom-repeat" items="[[_repos]]" as="repo">
+              <gr-repo-chip
+                  repo="[[repo]]"
+                  on-keydown="_handleChipKeydown"
+                  on-remove="_handleOnRemove"
+                  tabindex="-1">
+              </gr-repo-chip>
+            </template>
+          </div>
+          <div hidden$="[[_repositorySelected]]">
+            <gr-autocomplete
+              id="input"
+              threshold="[[suggestFrom]]"
+              query="[[_getRepoSuggestions]]"
+              on-commit="_handleRepositorySelected"
+              clear-on-commit
+              warn-uncommitted
+              text="{{_inputText}}">
+           </gr-autocomplete>
+          </div>
+        </section>
+
+        <section>
+          <span class="title">Scheme*</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_scheme}}">
+            <input is="iron-input"
+                   id="checkerSchemaInput"
+                   disabled$="[[_edit]]"
+                   autocomplete="on"
+                   bind-value="{{_scheme}}">
+          </iron-input>
+        </section>
+
+        <section>
+          <span class="title">ID*</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_id}}">
+            <input is="iron-input"
+                   id="checkerIdInput"
+                   disabled$="[[_edit]]"
+                   autocomplete="on"
+                   bind-value="{{_id}}">
+          </iron-input>
+        </section>
+
+        <section>
+          <span class="title">Url</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_url}}">
+            <input is="iron-input"
+                    id="checkerUrlInput"
+                    autocomplete="on"
+                    bind-value="{{_url}}">
+          </iron-input>
+        </section>
+
+        <section>
+          <span class="title"> UUID {{_uuid}}</span>
+        </section>
+
+        <section>
+          <span class="title">Status</span>
+          <gr-dropdown-list
+            items="[[_statuses]]"
+            on-value-change="_handleStatusChange"
+            text="Status"
+            value="[[_status]]">
+          </gr-dropdown-list>
+        </section>
+
+        <section>
+          <span class="title">Required</span>
+          <input
+          on-click = "_handleRequiredCheckBoxClicked"
+          type="checkbox"
+          id="privateChangeCheckBox"
+          checked$="[[_required]]">
+        </section>
+
+        <section>
+          <span class="title">Query</span>
+          <iron-input autocomplete="on"
+                      bind-value="{{_query}}">
+            <input is="iron-input"
+                    id="checkerQueryInput"
+                    autocomplete="on"
+                    bind-value="{{_query}}">
+          </iron-input>
+        </section>
+
+      </div>
+    </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-create-checkers-dialog.js"></script>
+</dom-module>
diff --git a/gr-checks/gr-create-checkers-dialog.js b/gr-checks/gr-create-checkers-dialog.js
new file mode 100644
index 0000000..cdb4127
--- /dev/null
+++ b/gr-checks/gr-create-checkers-dialog.js
@@ -0,0 +1,258 @@
+(function() {
+  'use strict';
+
+  const REPOS_PER_PAGE = 6;
+  const CREATE_CHECKER_URL = '/plugins/checks/checkers/';
+  const SCHEME_PATTERN = /^[\w-_.]*$/;
+
+  Polymer({
+    is: 'gr-create-checkers-dialog',
+    _legacyUndefinedCheck: true,
+
+    properties: {
+      checker: {
+        type: Object,
+        observer: '_checkerChanged'
+      },
+      _name: String,
+      _scheme: String,
+      _id: String,
+      _uuid: {
+        type: String,
+        value: ""
+      },
+      pluginRestApi: Object,
+      _url: String,
+      _description: String,
+      _getRepoSuggestions: {
+        type: Function,
+        value() {
+          return this._repoSuggestions.bind(this)
+        }
+      },
+      // The backend might support multiple repos in the future
+      // which is why I decided to keep it as an array.
+      _repos: {
+        type: Array,
+        value: [],
+        notify: true,
+      },
+      _repositorySelected: {
+        type: Boolean,
+        value: false
+      },
+      _handleOnRemove: Function,
+      _errorMsg: {
+        type: String,
+        value: ''
+      },
+      _statuses: {
+        type: Array,
+        value: [
+          {
+            "text": "ENABLED",
+            "value": "ENABLED"
+          },
+          {
+            "text": "DISABLED",
+            "value": "DISABLED"
+          }
+        ],
+        readOnly: true
+      },
+      _required: {
+        type: Boolean,
+        value: false
+      },
+      _status: String,
+      _edit: {
+        type: Boolean,
+        value: false
+      },
+      _query: String
+    },
+
+    behaviours: [
+      Gerrit.FireBehavior,
+    ],
+    /**
+    * Fired when the cancel button is pressed.
+    *
+    * @event cancel
+    */
+
+
+    observers: [
+      '_updateUUID(_scheme, _id)',
+    ],
+
+    _checkerChanged() {
+      if (!this.checker) {
+        console.warn("checker not set");
+        return;
+      }
+      this._edit = true;
+      this._scheme = this.checker.uuid.split(':')[0];
+      this._id = this.checker.uuid.split(':')[1];
+      this._name = this.checker.name;
+      this._description = this.checker.description || '';
+      this._url = this.checker.url || '';
+      this._query = this.checker.query || '';
+      this._required = this.checker.blocking &&
+                        this.checker.blocking.length > 0;
+      if (this.checker.repository) {
+        this._repositorySelected = true;
+        this.set('_repos', [{name: this.checker.repository}]);
+      }
+      this._status = this.checker.status;
+    },
+
+    _updateUUID(_scheme, _id) {
+      this._uuid = _scheme + ":" + _id;
+    },
+
+    _handleStatusChange(e) {
+      this._status = e.detail.value;
+    },
+
+    _validateRequest() {
+      if (!this._name) {
+        this._errorMsg = 'Name cannot be empty';
+        return false;
+      }
+      if (this._description && this._description.length > 1000) {
+        this._errorMsg = 'Description should be less than 1000 characters';
+        return false;
+      }
+      if (!this._repositorySelected) {
+        this._errorMsg = 'Select a repository';
+        return false;
+      }
+      if (!this._scheme) {
+        this._errorMsg = 'Scheme cannot be empty.';
+        return false;
+      }
+      if (this._scheme.match(SCHEME_PATTERN) == null) {
+        this._errorMsg =
+          'Scheme must contain [A-Z], [a-z], [0-9] or {"-" , "_" , "."}';
+        return false;
+      }
+      if (this._scheme.length > 100) {
+        this._errorMsg = 'Scheme must be shorter than 100 characters';
+        return false;
+      }
+      if (!this._id) {
+        this._errorMsg = 'ID cannot be empty.';
+        return false;
+      }
+      if (this._id.match(SCHEME_PATTERN) == null) {
+        this._errorMsg =
+          'ID must contain [A-Z], [a-z], [0-9] or {"-" , "_" , "."}';
+        return false;
+      }
+      return true;
+    },
+
+    // TODO(dhruvsri): make sure dialog is scrollable.
+
+    _createChecker(checker) {
+      return this.pluginRestApi.send(
+        'POST',
+        CREATE_CHECKER_URL,
+        checker,
+      )
+    },
+
+    _editChecker(checker) {
+      const url = CREATE_CHECKER_URL + checker.uuid;
+      return this.pluginRestApi.send(
+        'POST',
+        url,
+        checker
+      )
+    },
+
+    handleEditChecker() {
+      if (!this._validateRequest()) return;
+      this._editChecker(this._getCheckerRequestObject()).then(
+        res => {
+          if (res) {
+            this._errorMsg = '';
+            this.fire('cancel', {reload: true}, {bubbles: true});
+          }
+        },
+        error => {
+          this._errorMsg = error;
+        }
+      )
+    },
+
+    _getCheckerRequestObject() {
+      return {
+        "name" : this._name,
+        "description" : this._description || '',
+        "uuid" : this._uuid,
+        "repository": this._repos[0].name,
+        "url" : this._url,
+        "status": this._status,
+        "blocking": this._required ? ["STATE_NOT_PASSING"] : [],
+        "query": this._query
+      }
+    },
+
+    handleCreateChecker() {
+      if (!this._validateRequest()) return;
+      // Currently after creating checker there is no reload happening (as
+      // this would result in the user exiting the screen).
+      this._createChecker(this._getCheckerRequestObject()).then(
+        res => {
+          if (res) this._cleanUp();
+        },
+        error => {
+          this._errorMsg = error;
+        }
+      )
+    },
+
+    _cleanUp() {
+      this._name = '';
+      this._scheme = '';
+      this._id = '';
+      this._uuid = '';
+      this._description = '';
+      this._repos = [];
+      this._repositorySelected = false;
+      this._errorMsg = '';
+      this._required = false;
+      this._query = '';
+      this._status = '';
+      this.fire('cancel', {reload: true}, {bubbles: true});
+    },
+
+    _repoSuggestions(filter) {
+      const _makeSuggestion = repo => {return {name: repo.name, value: repo}};
+      return this.pluginRestApi.getRepos(filter, REPOS_PER_PAGE).then(
+        repos => repos.map(repo =>  _makeSuggestion(repo))
+      )
+    },
+
+    _handleRepositorySelected(e) {
+      this.push('_repos', e.detail.value);
+      this._repositorySelected = true;
+    },
+
+    _handleRequiredCheckBoxClicked() {
+      this._required = !this._required;
+    },
+
+    _handleOnRemove(e) {
+      let idx = this._repos.indexOf(e.detail.repo);
+      if (idx == -1) return;
+      this.splice('_repos', idx, 1);
+      if (this._repos.length == 0) {
+        this._repositorySelected = false;
+      }
+    },
+
+  });
+})();
diff --git a/gr-checks/gr-repo-chip.html b/gr-checks/gr-repo-chip.html
new file mode 100644
index 0000000..b695fa9
--- /dev/null
+++ b/gr-checks/gr-repo-chip.html
@@ -0,0 +1,25 @@
+<dom-module id="gr-repo-chip">
+  <template>
+    <style>
+      iron-icon {
+        height: 1.2rem;
+        width: 1.2rem;
+      }
+      :host {
+        display: inline-block;
+      }
+    </style>
+    <span> {{repo.name}} </span>
+    <gr-button
+      id="remove"
+      link
+      hidden$="[[!removable]]"
+      tabindex="-1"
+      aria-label="Remove"
+      class="remove"
+      on-tap="_handleRemoveTap">
+      <iron-icon icon="gr-icons:close"></iron-icon>
+    </gr-button>
+  </template>
+  <script src="gr-repo-chip.js"></script>
+</dom-module>
\ No newline at end of file
diff --git a/gr-checks/gr-repo-chip.js b/gr-checks/gr-repo-chip.js
new file mode 100644
index 0000000..d63a476
--- /dev/null
+++ b/gr-checks/gr-repo-chip.js
@@ -0,0 +1,24 @@
+(function() {
+  'use strict';
+
+  /**
+   * autocomplete chip for getting repository suggestions
+   */
+  Polymer({
+    is: 'gr-repo-chip',
+    _legacyUndefinedCheck: true,
+    properties: {
+      // repo type is ProjectInfo
+      repo: Object,
+      removable: {
+        type: Boolean,
+        value: true,
+      },
+    },
+    _handleRemoveTap(e) {
+      e.preventDefault();
+      this.fire('remove', {repo: this.repo});
+    },
+  })
+
+})();
\ No newline at end of file