Merge changes I180de2b6,I04470446,I8184cc4e

* changes:
  Add labels to the settings web interface
  Properly indent attributes
  Fix the way the rest API is called
diff --git a/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.html b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.html
new file mode 100644
index 0000000..79bc203
--- /dev/null
+++ b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.html
@@ -0,0 +1,219 @@
+<!--
+@license
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!-- TODO(maximeg) Import the web components when this issue is solved:
+        https://bugs.chromium.org/p/gerrit/issues/detail?id=8096 -->
+
+<dom-module id="gr-simple-submit-rules-label-config">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-form-styles"></style>
+    <style>
+      :host {
+        border: 1px solid var(--border-color);
+        display: block;
+        margin-bottom: 1em;
+        padding: 1em 1em;
+      }
+
+      fieldset {
+        border: 1px solid var(--border-color);
+      }
+    </style>
+
+    <main class="gr-form-styles">
+      <h3 id="options">Label [[labelName]]</h3>
+
+      <fieldset id="simple-submit-rules">
+
+        <section>
+          <span class="title">Should a vote with the maximum value be required?</span>
+          <span class="value">
+            <input id="maxVoteRequired"
+              type="checkbox"
+              checked="{{_maxVoteRequired::change}}"
+              disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <section>
+          <span class="title">Should votes with the lowest value block submission?</span>
+          <span class="value">
+            <input id="negativeBlocks"
+                type="checkbox"
+                checked="{{_negativeBlocks::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <section>
+          <span class="title">(Expert users) Function name</span>
+          <span class="value">
+            <gr-select id="functionName"
+                bind-value="{{_labelConfig.function}}">
+              <select disabled$="[[readOnly]]">
+                <option value="MaxNoBlock">MaxNoBlock</option>
+                <option value="MaxWithBlock">MaxWithBlock</option>
+                <option value="AnyWithBlock">AnyWithBlock</option>
+                <option value="NoBlock">NoBlock</option>
+                <option value="NoOp">NoOp</option>
+              </select>
+            </gr-select>
+          </span>
+        </section>
+
+        <section>
+          <span class="title">Allow approval by the change owner</span>
+          <span class="value">
+            <input id="allowUnresolvedComments"
+                type="checkbox"
+                checked="{{_labelConfig.ignore_self_approval::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+      </fieldset>
+
+      <fieldset>
+        <section>
+          <span class="title">
+            When a new patchset is uploaded, Gerrit should copy votes ...
+          </span>
+        </section>
+
+        <!-- copyMinScore -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Should votes of the minimal value be kept?"
+                max-width="20em"
+                show-icon>
+              with minimal value
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+          <input id="copyMinScore"
+              type="checkbox"
+              checked="{{_copyScores.copyMinScore::change}}"
+              disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <!-- copyMaxScore -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Should votes of the maximal value be kept?"
+                max-width="20em"
+                show-icon>
+              with maximal value
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+            <input id="copyMaxScore"
+                type="checkbox"
+                checked="{{_copyScores.copyMaxScore::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <!-- copyAllScoresOnTrivialRebase -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Should votes be kept when a trivial rebase
+                         is done (same commit message and content, different parent)?"
+                max-width="20em"
+                show-icon>
+              on trivial rebase
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+            <input id="copyAllScoresOnTrivialRebase"
+                type="checkbox"
+                checked="{{_copyScores.copyAllScoresOnTrivialRebase::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <!-- copyAllScoresIfNoCodeChange -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Should votes be kept when the commit message is modified?
+                         Changing the parent or changing files invalidates this."
+                max-width="20em"
+                show-icon>
+              when only the commit message is modified
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+            <input id="copyAllScoresIfNoCodeChange"
+                type="checkbox"
+                checked="{{_copyScores.copyAllScoresIfNoCodeChange::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <!-- copyAllScoresIfNoChange -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Should votes be kept when the commit metadata (author, commit
+                        date) are modified? Changing anything else from the commit
+                        (message, content, parent) invalidates this."
+                max-width="20em"
+                show-icon>
+              when only commit metatada are modified
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+            <input id="copyAllScoresIfNoChange"
+                type="checkbox"
+                checked="{{_copyScores.copyAllScoresIfNoChange::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+        <!-- copyAllScoresOnMergeFirstParentUpdate -->
+        <section>
+          <span class="title">
+            <gr-tooltip-content class="draftTooltip"
+                has-tooltip
+                title="Only applies to Merge commits. Should votes be kept when the
+                        destination commit of a merge commit is changed?"
+                max-width="20em"
+                show-icon>
+              on rebased merge commits
+            </gr-tooltip-content>
+          </span>
+          <span class="value">
+            <input id="copyAllScoresOnMergeFirstParentUpdate"
+                type="checkbox"
+                checked="{{_copyScores.copyAllScoresOnMergeFirstParentUpdate::change}}"
+                disabled$="[[readOnly]]">
+          </span>
+        </section>
+
+      </fieldset>
+    </main>
+  </template>
+  <script src="./gr-simple-submit-rules-label-config.js"></script>
+</dom-module>
diff --git a/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.js b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.js
new file mode 100644
index 0000000..7e11c39
--- /dev/null
+++ b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.js
@@ -0,0 +1,133 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+  'use strict';
+
+  const COPY_SCORES = [
+    'copyMinScore',
+    'copyMaxScore',
+    'copyAllScoresOnTrivialRebase',
+    'copyAllScoresIfNoCodeChange',
+    'copyAllScoresIfNoChange',
+    'copyAllScoresOnMergeFirstParentUpdate'
+  ];
+
+  Polymer({
+    is: 'gr-simple-submit-rules-label-config',
+
+    properties: {
+      labelName: String,
+      /** @type {?} */
+      repoConfig: {
+        type: Object,
+        notify: true,
+      },
+      readOnly: Boolean,
+      // The two "_updating" booleans are there to prevent an infinite loop:
+      // when the user changes a value, we update another value and this
+      // update in turn triggers the function again.
+      _updatingFunction: {
+        type: Boolean,
+        value: false
+      },
+      _updatingCopyScores: {
+        type: Boolean,
+        value: false
+      },
+      _labelConfig: {
+        type: Object,
+        computed: '_computeLabelConfig(repoConfig.labels, labelName)'
+      },
+      _copyScores: {
+        type: Object,
+        value: {},
+      },
+      _negativeBlocks: Boolean,
+      _maxVoteRequired: Boolean,
+    },
+
+    observers: [
+      '_observeFunctionChange(_labelConfig.function)',
+      '_observeFunctionDescriptorChange(_negativeBlocks, _maxVoteRequired)',
+      '_observeCopyScoresChange(_labelConfig.copy_scores)',
+      '_observeCopyScoresChangeInUi(_copyScores.*)',
+    ],
+
+    _observeFunctionDescriptorChange(negativeBlocks, maxVoteRequired) {
+      if (this._labelConfig === undefined) { return; }
+      if (this._updatingFunction) { return; }
+      this._updatingFunction = true;
+      let fName = '';
+
+      if (maxVoteRequired) {
+        fName = negativeBlocks ? 'MaxWithBlock' : 'MaxNoBlock';
+      } else {
+        fName = negativeBlocks ? 'AnyWithBlock' : 'NoBlock';
+      }
+      this.set('_labelConfig.function', fName);
+
+      this._updatingFunction = false;
+    },
+
+    _observeFunctionChange(_function) {
+      if (this._updatingFunction) { return; }
+      this._updatingFunction = true;
+
+      this._negativeBlocks = _function.indexOf('WithBlock') !== -1;
+      this._maxVoteRequired = _function.indexOf('Max') !== -1;
+
+      this._updatingFunction = false;
+    },
+
+    _computeLabelConfig(labels, labelName) {
+      this.linkPaths(['repoConfig.labels', labelName], '_labelConfig');
+      this.linkPaths('_labelConfig', ['repoConfig.labels', labelName]);
+      return labels[this.labelName] || {};
+    },
+
+    _observeCopyScoresChange() {
+      if (this._updatingCopyScores) { return; }
+      this._updatingCopyScores = true;
+
+      for (let key of COPY_SCORES) {
+        this.set(['_copyScores', key], false);
+      }
+      for (let value of this._labelConfig.copy_scores) {
+        this.set(['_copyScores', value], true);
+      }
+
+      this._updatingCopyScores = false;
+    },
+
+    _observeCopyScoresChangeInUi() {
+      if (this._updatingCopyScores) { return; }
+      this._updatingCopyScores = true;
+
+
+      let newCopyScores = [];
+      for (let key in this._copyScores) {
+        if (this._copyScores[key]) {
+          newCopyScores.push(key);
+        }
+      }
+      this.set('_labelConfig.copy_scores', newCopyScores);
+
+      this._updatingCopyScores = false;
+    },
+  });
+})();
diff --git a/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config_test.html b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config_test.html
new file mode 100644
index 0000000..97408cf
--- /dev/null
+++ b/gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config_test.html
@@ -0,0 +1,270 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2016 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<meta name="viewport"
+  content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-simple-submit-rules-label-config</title>
+
+<script src="../../../polygerrit-ui/app/bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../polygerrit-ui/app/bower_components/web-component-tester/browser.js"></script>
+<link rel="import"
+  href="../../../polygerrit-ui/app/test/common-test-setup.html" />
+<!-- TODO(maximeg) find if there is a better way to do this, like .. not in tests -->
+<link rel="import"
+  href="../../../polygerrit-ui/app/elements/shared/gr-select/gr-select.html" />
+<link rel="import"
+  href="../../../polygerrit-ui/app/styles/shared-styles.html" />
+<link rel="import"
+  href="../../../polygerrit-ui/app/styles/gr-form-styles.html" />
+
+<link rel="import"
+  href="gr-simple-submit-rules-label-config.html">
+
+<script>void (0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-simple-submit-rules-label-config label-name="Verified"
+      repo-config='{"labels": {"Verified": {"function": "MaxNoBlock", "copy_scores": []}}}'
+      readOnly="false">
+    </gr-simple-submit-rules-label-config>
+  </template>
+</test-fixture>
+
+<script>
+  const COPY_SCORES = [
+    'copyMinScore',
+    'copyMaxScore',
+    'copyAllScoresOnTrivialRebase',
+    'copyAllScoresIfNoCodeChange',
+    'copyAllScoresIfNoChange',
+    'copyAllScoresOnMergeFirstParentUpdate'
+  ];
+
+  suite('gr-simple-submit-rules-label-config tests', () => {
+    let element;
+    let sandbox;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+
+      element = fixture('basic');
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('section title is correct', () => {
+      assert.equal(element.$$('#options').innerText.indexOf('Label Verified'), 0);
+    });
+
+    // The following tests check that changing the function name (from the REST API)
+    // has an impact on the displayed settings
+    test('function "MaxWithBlock" is properly mapped to UI', done => {
+      element.set('_labelConfig.function', 'MaxWithBlock');
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxWithBlock');
+        done();
+      });
+    });
+
+    test('function "MaxNoBlock" is properly mapped to UI', done => {
+      element.set('_labelConfig.function', 'MaxNoBlock');
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxNoBlock');
+        done();
+      });
+    });
+
+    test('function "NoBlock" is properly mapped to UI', done => {
+      element.set('_labelConfig.function', 'NoBlock');
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'NoBlock');
+        done();
+      });
+    });
+
+    test('function "AnyWithBlock" is properly mapped to UI', done => {
+      element.set('_labelConfig.function', 'AnyWithBlock');
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'AnyWithBlock');
+        done();
+      });
+    });
+
+    // The following tests check that changing the function from the UI has a
+    // visible impact on the easy checkboxes options, while keeping the function
+    // name intact.
+    test('picking function "MaxWithBlock" correctly updates the UI', done => {
+      element.$$('#functionName select').value = 'MaxWithBlock';
+      element.$$('#functionName').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxWithBlock');
+        done();
+      });
+    });
+
+    test('picking function "NoBlock" correctly updates the UI', done => {
+      element.$$('#functionName select').value = 'NoBlock';
+      element.$$('#functionName').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'NoBlock');
+        done();
+      });
+    });
+
+    test('picking function "AnyWithBlock" correctly updates the UI', done => {
+      element.$$('#functionName select').value = 'AnyWithBlock';
+      element.$$('#functionName').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'AnyWithBlock');
+        done();
+      });
+    });
+
+    test('picking function "MaxNoBlock" correctly updates the UI', done => {
+      element.$$('#functionName select').value = 'MaxNoBlock';
+      element.$$('#functionName').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxNoBlock');
+        done();
+      });
+    });
+
+    // The following tests check that the easy way to define the function
+    // ("negative blocks?" and "max vote required?") maps to the right function,
+    // without impacting the user choices.
+    test('function "MaxWithBlock" is properly suggested from UI', done => {
+      element.$$('#negativeBlocks').checked = true;
+      element.$$('#negativeBlocks').dispatchEvent(new Event('change'));
+
+      element.$$('#maxVoteRequired').checked = 'true';
+      element.$$('#maxVoteRequired').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxWithBlock');
+        done();
+      });
+    });
+
+    test('function "NoBlock" is properly suggested from UI', done => {
+      element.$$('#negativeBlocks').checked = false;
+      element.$$('#negativeBlocks').dispatchEvent(new Event('change'));
+
+      element.$$('#maxVoteRequired').checked = false;
+      element.$$('#maxVoteRequired').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'NoBlock');
+        done();
+      });
+    });
+
+    test('function "AnyWithBlock" is properly suggested from UI', done => {
+      element.$$('#negativeBlocks').checked = true;
+      element.$$('#negativeBlocks').dispatchEvent(new Event('change'));
+
+      element.$$('#maxVoteRequired').checked = false;
+      element.$$('#maxVoteRequired').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.ok(element.$$('#negativeBlocks').checked);
+        assert.equal(element.$$('#maxVoteRequired').checked, false);
+        assert.equal(element.$$('#functionName select').value, 'AnyWithBlock');
+        done();
+      });
+    });
+
+    test('function "MaxNoBlock" is properly suggested from UI', done => {
+      element.$$('#negativeBlocks').checked = false;
+
+      element.$$('#maxVoteRequired').checked = 'true';
+      element.$$('#maxVoteRequired').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element.$$('#negativeBlocks').checked, false);
+        assert.ok(element.$$('#maxVoteRequired').checked);
+        assert.equal(element.$$('#functionName select').value, 'MaxNoBlock');
+        done();
+      });
+    });
+
+    // The following tests check that "copy scores" are correctly mapped *to* and
+    // *from* the UI, for the two possible boolean values.
+    for (let copyScoreName of COPY_SCORES) {
+      let elName = '#' + copyScoreName;
+
+      test('copyScore.' + copyScoreName + ' [false] is reflected *in* the UI', () => {
+        assert.equal(element.$$(elName).checked, false);
+      });
+
+      test('copyScore.' + copyScoreName + ' [true] is reflected in the UI', done => {
+        element.set('_labelConfig.copy_scores', [copyScoreName]);
+
+        flush(function () {
+          assert.ok(element.$$(elName).checked);
+          done();
+        });
+      });
+    }
+
+
+    for (let copyScoreName of COPY_SCORES) {
+      let elName = '#' + copyScoreName;
+
+      test('copyScore.' + copyScoreName + ' is reflected *from* the UI', done => {
+        element.$$(elName).checked = 'true';
+        element.$$(elName).dispatchEvent(new Event('change'));
+
+        flush(function () {
+          assert.deepEqual(element.repoConfig.labels.Verified.copy_scores, [copyScoreName]);
+          done();
+        });
+      });
+    }
+
+  });
+</script>
diff --git a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html
index 3e1ade1..23cc805 100644
--- a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html
@@ -15,6 +15,11 @@
 limitations under the License.
 -->
 
+<link rel="import"
+  href="../gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config.html">
+<!-- TODO(maximeg) Import the web components when this issue is solved:
+        https://bugs.chromium.org/p/gerrit/issues/detail?id=8096 -->
+
 <dom-module id="gr-simple-submit-rules-repo-config">
   <template>
     <style include="shared-styles"></style>
@@ -28,7 +33,7 @@
           <span class="title">Allow submission with unresolved comments</span>
           <span class="value">
             <gr-select id="allowUnresolvedComments"
-              bind-value="{{_repoConfig.comments.block_if_unresolved_comments}}">
+                bind-value="{{_repoConfig.comments.block_if_unresolved_comments}}">
               <select disabled$="[[_readOnly]]">
                 <option value="true">Yes</option>
                 <option value="false">No</option>
@@ -36,10 +41,22 @@
             </gr-select>
           </span>
         </section>
+
+        <template is="dom-repeat"
+            id="allLabels"
+            items="[[_labels]]"
+            initial-count="5"
+            target-framerate="60">
+          <gr-simple-submit-rules-label-config mutable-data
+              label-name="[[item]]"
+              repo-config="{{_repoConfig}}"
+              read-only="[[_readOnly]]">
+          </gr-simple-submit-rules-label-config>
+        </template>
       </fieldset>
 
       <gr-button on-tap="_handleSaveRepoConfig"
-        disabled$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">
+          disabled$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">
         Save Changes
       </gr-button>
 
diff --git a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.js b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.js
index 746e5e2..f745939 100644
--- a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.js
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.js
@@ -32,7 +32,12 @@
         type: Boolean,
         value: true,
       },
-      _pluginRestApi: Object,
+     _restApi: Object,
+      _labels: {
+        type: Array,
+        value() { return []; },
+        computed: '_computeLabelNames(_repoConfig.*)',
+      },
     },
 
     observers: [
@@ -75,7 +80,7 @@
       promises.push(this._pluginRestApi().get(this._endpointUrl())
         .then(config => {
           if (!config) { return; }
-          this._repoConfig = config;
+          this.set('_repoConfig', config);
           this._loading = false;
         }));
 
@@ -87,7 +92,7 @@
     },
 
     _endpointUrl() {
-      return 'projects/' + encodeURIComponent(this.repoName) + '/simple-submit-rules';
+      return '/projects/' + encodeURIComponent(this.repoName) + '/simple-submit-rules';
     },
 
     _handleSaveRepoConfig() {
@@ -110,15 +115,22 @@
     },
 
     _pluginRestApi() {
-      if (this._pluginRestApi === undefined) {
-        this._pluginRestApi = this.plugin.restApi();
+      if (this._restApi === undefined) {
+        this._restApi = this.plugin.restApi();
       }
-      return this._pluginRestApi;
+      return this._restApi;
     },
 
     _getRepoAccess(repoName) {
       return this._pluginRestApi().get('/access/?project=' + encodeURIComponent(repoName));
     },
 
+    _computeLabelNames() {
+      if (this._repoConfig && this._repoConfig.labels) {
+        return Object.keys(this._repoConfig.labels);
+      }
+      return [];
+    },
+
   });
 })();
diff --git a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html
index 4e35e86..f71b6f7 100644
--- a/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html
@@ -60,6 +60,7 @@
                 comments: {
                   block_if_unresolved_comments: false,
                 },
+                labels: {},
               });
             },
             getLoggedIn() { return Promise.resolve(true); },
@@ -98,6 +99,42 @@
       assert.equal(unresolvedCommentsEl.disabled, true);
     });
 
+    test('adds a label element', done => {
+      element.set(['_repoConfig', 'labels', 'Verified'], {
+        function: 'MaxNoBlock',
+        copy_scores: []
+      });
+      flush(function () {
+        let labelItems = element.querySelectorAll('gr-simple-submit-rules-label-config');
+        assert.ok(labelItems);
+        assert.equal(labelItems.length, 1);
+
+        let labelEl = labelItems[0];
+        assert.ok(labelEl);
+        assert.equal(labelEl.labelName, 'Verified');
+        assert.equal(labelEl.readOnly, false);
+        done();
+      });
+    });
+
+    test('adds two labels elements', done => {
+      element.set(['_repoConfig', 'labels', 'Verified'], {
+        function: 'MaxNoBlock',
+        copy_scores: []
+      });
+
+      element.set(['_repoConfig', 'labels', 'Code-Review'], {
+        function: 'MaxNoBlock',
+        copy_scores: []
+      });
+
+      flush(function () {
+        let labelItems = element.querySelectorAll('gr-simple-submit-rules-label-config');
+        assert.equal(labelItems.length, 2);
+        done();
+      });
+    });
+
     test('unresolved comment uses the repoConfig value (false)', done => {
       element.set('_repoConfig.comments.block_if_unresolved_comments', false);
 
diff --git a/tests.html b/tests.html
index e4dbfc4..32c3317 100644
--- a/tests.html
+++ b/tests.html
@@ -11,6 +11,7 @@
   <script>
     WCT.loadSuites([
       'gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html',
+      'gr-simple-submit-rules-label-config/gr-simple-submit-rules-label-config_test.html'
     ]);
   </script>
 </body>