Merge "Add a web interface to configure the comments rule"
diff --git a/BUILD b/BUILD
index 273cf24..38fcfd6 100644
--- a/BUILD
+++ b/BUILD
@@ -1,5 +1,7 @@
-load("//tools/bzl:plugin.bzl", "gerrit_plugin", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS")
+load("//tools/bzl:genrule2.bzl", "genrule2")
+load("//tools/bzl:js.bzl", "polygerrit_plugin")
 load("//tools/bzl:junit.bzl", "junit_tests")
+load("//tools/bzl:plugin.bzl", "gerrit_plugin", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS")
 
 gerrit_plugin(
     name = "simple-submit-rules",
@@ -9,6 +11,7 @@
         "Gerrit-Module: com.googlesource.gerrit.plugins.simplesubmitrules.Module",
         "Gerrit-BatchModule: com.googlesource.gerrit.plugins.simplesubmitrules.BatchModule",
     ],
+    resource_jars = [":ssr-static"],
     resources = glob(["src/main/resources/**/*"]),
 )
 
@@ -36,3 +39,24 @@
         ":simple-submit-rules__plugin",
     ],
 )
+
+genrule2(
+    name = "ssr-static",
+    srcs = [":ssr"],
+    outs = ["ssr-static.jar"],
+    cmd = " && ".join([
+        "mkdir $$TMP/static",
+        "cp -rp $(locations :ssr) $$TMP/static",
+        "cd $$TMP",
+        "zip -Drq $$ROOT/$@ -g .",
+    ]),
+)
+
+polygerrit_plugin(
+    name = "ssr",
+    srcs = glob([
+        "**/*.html",
+        "**/*.js",
+    ]),
+    app = "plugin.html",
+)
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
new file mode 100644
index 0000000..3e1ade1
--- /dev/null
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html
@@ -0,0 +1,49 @@
+<!--
+@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.
+-->
+
+<dom-module id="gr-simple-submit-rules-repo-config">
+  <template>
+    <style include="shared-styles"></style>
+    <style include="gr-form-styles"></style>
+
+    <main class="gr-form-styles">
+      <h3 id="options">Simple Submit Rules</h3>
+
+      <fieldset id="unresolved_comments">
+        <section>
+          <span class="title">Allow submission with unresolved comments</span>
+          <span class="value">
+            <gr-select id="allowUnresolvedComments"
+              bind-value="{{_repoConfig.comments.block_if_unresolved_comments}}">
+              <select disabled$="[[_readOnly]]">
+                <option value="true">Yes</option>
+                <option value="false">No</option>
+              </select>
+            </gr-select>
+          </span>
+        </section>
+      </fieldset>
+
+      <gr-button on-tap="_handleSaveRepoConfig"
+        disabled$="[[_computeButtonDisabled(_readOnly, _configChanged)]]">
+        Save Changes
+      </gr-button>
+
+    </main>
+  </template>
+  <script src="./gr-simple-submit-rules-repo-config.js"></script>
+</dom-module>
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
new file mode 100644
index 0000000..960d654
--- /dev/null
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.js
@@ -0,0 +1,124 @@
+/**
+ * @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';
+  Polymer({
+    is: 'gr-simple-submit-rules-repo-config',
+
+    properties: {
+      repoName: String,
+      /** @type {?} */
+      _repoConfig: Object,
+      _configChanged: {
+        type: Boolean,
+        value: false,
+      },
+      _readOnly: {
+        type: Boolean,
+        value: true,
+      },
+      _pluginRestApi: Object,
+    },
+
+    observers: [
+      '_handleConfigChanged(_repoConfig.*)',
+    ],
+
+    attached() {
+      return this._loadRepo();
+    },
+
+    _isLoading() {
+      return this._loading || this._loading === undefined;
+    },
+
+    _handleConfigChanged() {
+      if (this._isLoading()) { return; }
+      this._configChanged = true;
+    },
+
+    _computeButtonDisabled(readOnly, configChanged) {
+      return readOnly || !configChanged;
+    },
+
+    _loadRepo() {
+      this.repoConfig = {};
+      if (!this.repoName) { return; }
+      const promises = [];
+
+      promises.push(this._pluginRestApi().getLoggedIn().then(loggedIn => {
+        this._loggedIn = loggedIn;
+        if (loggedIn) {
+          this._getRepoAccess(this.repoName).then(access => {
+            if (!access) { return; }
+            // If the user is not an owner, is_owner is not a property.
+            this._readOnly = !access[this.repoName].is_owner;
+          });
+        }
+      }));
+
+      promises.push(this._pluginRestApi().get(this._endpointUrl())
+        .then(config => {
+          if (!config) { return; }
+          this._repoConfig = config;
+          this._loading = false;
+        }));
+
+      return Promise.all(promises);
+    },
+
+    _formatRepoConfigForSave(repoConfig) {
+      return repoConfig;
+    },
+
+    _endpointUrl() {
+      return "projects/" + encodeURIComponent(this.repoName) + "/simple-submit-rules";
+    },
+
+    _handleSaveRepoConfig() {
+      this._loading = true;
+      return this._pluginRestApi().put(this._endpointUrl(), this._repoConfig)
+        .then(config => {
+          if (!config) { return Promise.resolve(); }
+
+          this.dispatchEvent(new CustomEvent('show-alert', {
+            detail: {
+              message: "Simple submit rules: configuration updated."
+            },
+            bubbles: true
+          }));
+
+          this._repoConfig = config;
+          this._loading = false;
+          this._configChanged = false;
+        })
+    },
+
+    _pluginRestApi() {
+      if (this._pluginRestApi === undefined) {
+        this._pluginRestApi = this.plugin.restApi();
+      }
+      return this._pluginRestApi;
+    },
+
+    _getRepoAccess(repoName) {
+      return this._pluginRestApi().get('/access/?project=' + encodeURIComponent(repoName));
+    },
+
+  });
+})();
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
new file mode 100644
index 0000000..4e35e86
--- /dev/null
+++ b/gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html
@@ -0,0 +1,139 @@
+<!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-repo-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-repo-config.html">
+
+<script>void (0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-simple-submit-rules-repo-config repo-name="test-repo"></gr-simple-submit-rules-repo-config>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-simple-submit-rules-repo-config tests', () => {
+    let element;
+    let sandbox;
+    let unresolvedCommentsEl;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+
+      stub('gr-simple-submit-rules-repo-config', {
+        _pluginRestApi() {
+          return {
+            get(url) {
+              return Promise.resolve({
+                comments: {
+                  block_if_unresolved_comments: false,
+                },
+              });
+            },
+            getLoggedIn() { return Promise.resolve(true); },
+          }
+        },
+
+        _getRepoAccess(repoName) {
+          return Promise.resolve({
+            'test-repo': {
+              is_owner: true,
+            },
+          });
+        },
+
+      });
+
+      element = fixture('basic');
+      unresolvedCommentsEl = element.$$('#unresolved_comments select');
+      return element._loadRepo();
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('unresolved comments option exists', () => {
+      const unresolvedCommentsEl = element.$$('#unresolved_comments');
+      assert.ok(unresolvedCommentsEl);
+    });
+
+    test('readOnly disables fields', () => {
+      element._readOnly = false;
+      assert.equal(unresolvedCommentsEl.disabled, false);
+
+      element._readOnly = true;
+      assert.equal(unresolvedCommentsEl.disabled, true);
+    });
+
+    test('unresolved comment uses the repoConfig value (false)', done => {
+      element.set('_repoConfig.comments.block_if_unresolved_comments', false);
+
+      flush(function () {
+        assert.equal(unresolvedCommentsEl.value, 'false');
+        done();
+      });
+    });
+
+    test('unresolved comment uses the repoConfig value (true)', done => {
+      element.set('_repoConfig.comments.block_if_unresolved_comments', true);
+
+      flush(function () {
+        assert.equal(unresolvedCommentsEl.value, 'true');
+        done();
+      });
+    });
+
+    test('unresolved comment sets the repoConfig value (true)', done => {
+      unresolvedCommentsEl.value = 'true';
+      element.$$('#allowUnresolvedComments').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element._repoConfig.comments.block_if_unresolved_comments, 'true');
+        done();
+      });
+    });
+
+    test('unresolved comment sets the repoConfig value (false)', done => {
+      unresolvedCommentsEl.value = 'false';
+      element.$$('#allowUnresolvedComments').dispatchEvent(new Event('change'));
+
+      flush(function () {
+        assert.equal(element._repoConfig.comments.block_if_unresolved_comments, 'false');
+        done();
+      });
+    });
+  });
+</script>
diff --git a/plugin.html b/plugin.html
new file mode 100644
index 0000000..ab42d17
--- /dev/null
+++ b/plugin.html
@@ -0,0 +1,27 @@
+<!--
+@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.
+-->
+
+<link rel="import"
+  href="./gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config.html">
+
+<dom-module id="simple-submit-rules">
+  <script>
+    Gerrit.install(plugin => {
+      plugin.registerCustomComponent('repo-config', 'gr-simple-submit-rules-repo-config');
+    });
+  </script>
+</dom-module>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/simplesubmitrules/Module.java b/src/main/java/com/googlesource/gerrit/plugins/simplesubmitrules/Module.java
index 5aa4cb0..f8721e6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/simplesubmitrules/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/simplesubmitrules/Module.java
@@ -14,7 +14,10 @@
 
 package com.googlesource.gerrit.plugins.simplesubmitrules;
 
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.inject.AbstractModule;
 import com.googlesource.gerrit.plugins.simplesubmitrules.config.ConfigServlet;
@@ -33,6 +36,8 @@
             put(ProjectResource.PROJECT_KIND, API_ENDPOINT).to(ConfigServlet.class);
           }
         });
+
+    DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("ssr.html"));
     install(new BatchModule());
   }
 }
diff --git a/tests.html b/tests.html
new file mode 100644
index 0000000..e4dbfc4
--- /dev/null
+++ b/tests.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <script src="../../polygerrit-ui/app//bower_components/webcomponentsjs/webcomponents-lite.js"></script>
+  <script src="../../polygerrit-ui/app/bower_components/web-component-tester/browser.js"></script>
+</head>
+
+<body>
+  <script>
+    WCT.loadSuites([
+      'gr-simple-submit-rules-repo-config/gr-simple-submit-rules-repo-config_test.html',
+    ]);
+  </script>
+</body>
+
+</html>