Allow to enable Zuul integration per project

Skip parsing the messages, if project wasn't enabled for Zuul.

Expose project config entry to enable the Zuul integration from the
project page.

As the consequence of this change the deployment is changed from
JavaScript to JAR artifact.

Bug: Issue 13980
Change-Id: I85a132565af217b703c93988ed5de7f7628efead
diff --git a/BUILD b/BUILD
index 1b6857b..31c2bd6 100644
--- a/BUILD
+++ b/BUILD
@@ -1,12 +1,41 @@
+load("//tools/bzl:genrule2.bzl", "genrule2")
 load("//tools/bzl:js.bzl", "polygerrit_plugin")
+load("//tools/bzl:plugin.bzl", "gerrit_plugin")
 load("//tools/js:eslint.bzl", "eslint")
 
-polygerrit_plugin(
+gerrit_plugin(
     name = "zuul-results-summary",
+    srcs = glob(["java/**/*.java"]),
+    manifest_entries = [
+        "Gerrit-PluginName: zuul-results-summary",
+        "Gerrit-Module: com.googlesource.gerrit.plugins.zuulresultssummary.Module",
+        "Implementation-Title: Zuul-Results-Summary plugin",
+        "Implementation-URL: https://gerrit.googlesource.com/plugins/zuul-results-summary",
+    ],
+    resource_jars = [":zuul-results-summary-static"],
+    resource_strip_prefix = "plugins/zuul-results-summary/resources",
+    resources = glob(["resources/**/*"]),
+)
+
+genrule2(
+    name = "zuul-results-summary-static",
+    srcs = [":zuul-results-summary-ui"],
+    outs = ["zuul-results-summary-static.jar"],
+    cmd = " && ".join([
+        "mkdir $$TMP/static",
+        "cp -r $(locations :zuul-results-summary-ui) $$TMP/static",
+        "cd $$TMP",
+        "zip -Drq $$ROOT/$@ -g .",
+    ]),
+)
+
+polygerrit_plugin(
+    name = "zuul-results-summary-ui",
     srcs = glob([
         "zuul-results-summary/*.js",
     ]),
     app = "zuul-results-summary/zuul-results-summary.js",
+    plugin_name = "zuul-results-summary",
 )
 
 # Define the eslinter for the plugin
diff --git a/java/com/googlesource/gerrit/plugins/zuulresultssummary/GetConfig.java b/java/com/googlesource/gerrit/plugins/zuulresultssummary/GetConfig.java
new file mode 100644
index 0000000..46a09b0
--- /dev/null
+++ b/java/com/googlesource/gerrit/plugins/zuulresultssummary/GetConfig.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 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.
+
+package com.googlesource.gerrit.plugins.zuulresultssummary;
+
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+class GetConfig implements RestReadView<ProjectResource> {
+  private final String pluginName;
+  private final PluginConfigFactory cfgFactory;
+
+  @Inject
+  GetConfig(@PluginName String pluginName, PluginConfigFactory cfgFactory) {
+    this.pluginName = pluginName;
+    this.cfgFactory = cfgFactory;
+  }
+
+  @Override
+  public Response<ZuulResultsSummaryConfig> apply(ProjectResource project)
+      throws NoSuchProjectException {
+    ZuulResultsSummaryConfig result = new ZuulResultsSummaryConfig();
+    result.enabled =
+        cfgFactory
+            .getFromProjectConfigWithInheritance(project.getNameKey(), pluginName)
+            .getBoolean(Module.KEY_PLUGIN_ENABLED, false);
+
+    return Response.ok(result);
+  }
+
+  static class ZuulResultsSummaryConfig {
+    boolean enabled;
+  }
+}
diff --git a/java/com/googlesource/gerrit/plugins/zuulresultssummary/Module.java b/java/com/googlesource/gerrit/plugins/zuulresultssummary/Module.java
new file mode 100644
index 0000000..246ce74
--- /dev/null
+++ b/java/com/googlesource/gerrit/plugins/zuulresultssummary/Module.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 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.
+
+package com.googlesource.gerrit.plugins.zuulresultssummary;
+
+import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
+
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
+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.config.ProjectConfigEntry;
+
+public class Module extends RestApiModule {
+  static final String KEY_PLUGIN_ENABLED = "enabled";
+
+  @Override
+  protected void configure() {
+    // Point to the JavaScript that provides the main functionality of this plugin.
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("zuul-results-summary.js"));
+
+    // Register the config endpoint used by the JavaScript client code.
+    get(PROJECT_KIND, "config").to(GetConfig.class);
+
+    // Configure UI element to be exposed on the project view
+    bind(ProjectConfigEntry.class)
+        .annotatedWith(Exports.named(KEY_PLUGIN_ENABLED))
+        .toInstance(
+            new ProjectConfigEntry(
+                "Enable Zuul results summary",
+                "false",
+                ProjectConfigEntryType.BOOLEAN,
+                null,
+                false,
+                "Parse comment messages and render CI build results on Zuul Summary tab."));
+  }
+}
diff --git a/resources/Documentation/about.md b/resources/Documentation/about.md
new file mode 100644
index 0000000..812846d
--- /dev/null
+++ b/resources/Documentation/about.md
@@ -0,0 +1,2 @@
+The @PLUGIN@ plugin populates tab that shows a summary of results posted
+by the Zuul CI system (https://zuul-ci.org).
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
new file mode 100644
index 0000000..8711f5c
--- /dev/null
+++ b/resources/Documentation/config.md
@@ -0,0 +1,17 @@
+Plugin @PLUGIN@
+===============
+
+The plugin populates tab that shows a summary of results posted
+by the Zuul CI system.
+
+It can be configured per project whether the Zuul CI integration is
+enabled or not. To enable the Zuul CI integration for a project the
+project must have the following entry in its `project.config` file in
+the `refs/meta/config` branch:
+
+```
+  [plugin "zuul-results-summary"]
+    enabled = true
+```
+
+Per default projects are not enabled for Zuul CI integration.
diff --git a/resources/Documentation/rest-api-config.md b/resources/Documentation/rest-api-config.md
new file mode 100644
index 0000000..99722ae
--- /dev/null
+++ b/resources/Documentation/rest-api-config.md
@@ -0,0 +1,64 @@
+@PLUGIN@ - /config/ REST API
+============================
+
+This page describes the '/config/' REST endpoint that is added by the
+@PLUGIN@ plugin.
+
+Please also take note of the general information on the
+[REST API](../../../Documentation/rest-api.html).
+
+<a id="project-endpoints"> @PLUGIN@ Endpoints
+--------------------------------------------
+
+### <a id="get-config"> Get Config
+_GET /projects/[ProjectName](#project-name)/@PLUGIN@~config_
+
+Gets per project configuration of the @PLUGIN@ plugin.
+
+#### Request
+
+```
+  GET /projects/foo/@PLUGIN@~config HTTP/1.0
+```
+
+As response a [ConfigInfo](#config-info) entity is returned that
+contains the configuration of the @PLUGIN@ plugin.
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "enabled": true
+  }
+```
+
+### <a id="project-name"></a>ProjectName
+
+The name of the project.
+
+If the name ends with `.git`, the suffix will be automatically removed.
+
+
+### <a id="config-info"></a>ConfigInfo
+
+The `ConfigInfo` entity contains the configuration of the @PLUGIN@
+plugin.
+
+|Field Name       |Description|
+|-----------------|-----------|
+|enabled          | Whether the project is enabled for Zuul CI integration|
+
+
+SEE ALSO
+--------
+
+* [Config related REST endpoints](../../../Documentation/rest-api-config.html)
+
+GERRIT
+------
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/zuul-results-summary/zuul-results-summary.js b/zuul-results-summary/zuul-results-summary.js
index 559c3d5..b338ea7 100644
--- a/zuul-results-summary/zuul-results-summary.js
+++ b/zuul-results-summary/zuul-results-summary.js
@@ -26,9 +26,10 @@
    */
   static get properties() {
     return {
+      plugin: Object,
       change: {
         type: Object,
-        observer: '_changeChanged',
+        observer: '_processChange',
       },
       revision: Object,
     };
@@ -157,6 +158,40 @@
   </template>`;
   }
 
+  /**
+   * Process the change. Retrieve project configuration, and if it's
+   * enabled for Zuul, parse the messages and render Zuul Summary tab.
+   *
+   * @param {Object} change
+   */
+  async _processChange(change) {
+    // TODO(davido): Cache results of project config request
+    const processMessages = await this._projectEnabled(change.project);
+    if (processMessages) {
+      this._processMessages(change);
+    }
+  }
+
+  /**
+   * Returns whether the project is enabled for Zuul.
+   *
+   * @param {string} project
+   * @return {promise<boolean>} Resolves to true if the project is enabled
+   *     otherwise, false.
+   */
+  async _projectEnabled(project) {
+    const configPromise = this.plugin.restApi().get(
+        `/projects/${encodeURIComponent(project)}/` +
+        `${encodeURIComponent(this.plugin.getPluginName())}~config`);
+    try {
+      const config = await configPromise;
+      return config && config.enabled;
+    } catch (error) {
+      console.log(error);
+      return false;
+    }
+  }
+
   /** Look for Zuul tag in message
    *
    * @param{ChangeMessageInfo} message
@@ -206,7 +241,7 @@
   }
 
   /** Change Modified */
-  _changeChanged(change, oldChange) {
+  _processMessages(change) {
     /*
      * change-view-tab-content gets passed ChangeInfo object [1],
      * registered in the property "change".  We walk the list of