Document UI Extension feature

Change-Id: I12877b51c81bc473a0590e51c3d2be609ba7edfc
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index ab2fd5a..c6cab59 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -328,7 +328,7 @@
   }
 ====
 
-If no Guice modules are declared in the manifest, UI commands may
+If no Guice modules are declared in the manifest, UI actions may
 use auto-registration by providing an `@Export` annotation:
 
 ====
@@ -351,7 +351,7 @@
 ====
 
 With a plugin-owned capability defined in this way, it is possible to restrict
-usage of an SSH command or UiAction to members of the group that were granted
+usage of an SSH command or `UiAction` to members of the group that were granted
 this capability in the usual way, using the `RequiresCapability` annotation:
 
 ====
@@ -361,7 +361,7 @@
   ...
 ====
 
-Or with UiAction:
+Or with `UiAction`:
 
 ====
   @RequiresCapability("printHello")
@@ -390,6 +390,186 @@
   ...
 ====
 
+[[ui_extension]]
+UI Extension
+------------
+
+Plugins can contribute their own UI commands on core Gerrit pages.
+This is useful for workflow customization or exposing plugin functionality
+through the UI in addition to SSH commands and the REST API.
+
+For instance a plugin to integrate Jira with Gerrit changes may contribute its
+own "File bug" button to allow filing a bug from the change page or plugins to
+integrate continuous integration systems may contribute a "Schedule" button to
+allow a CI build to be scheduled manually from the patch set panel.
+
+Two different places on core Gerrit pages are currently supported:
+
+* Change screen
+* Project info screen
+
+Plugins contribute UI actions by implementing the `UiAction` interface:
+
+====
+  @RequiresCapability("printHello")
+  class HelloWorldAction implements UiAction<RevisionResource>,
+      RestModifyView<RevisionResource, HelloWorldAction.Input> {
+    static class Input {
+      boolean french;
+      String message;
+    }
+
+    private Provider<CurrentUser> cu;
+
+    @Inject
+    HelloWorldAction(Provider<CurrentUser> user) {
+      this.user = user;
+    }
+
+    @Override
+    public String apply(RevisionResource rev, Input input) {
+      final String greeting = input.french
+          ? "Bonjour"
+          : "Hello";
+      return String.format("%s %s from change %s, patch set %d!",
+          greeting,
+          Strings.isNullOrEmpty(input.message)
+              ? Objects.firstNonNull(user.get().getUserName(), "world")
+              : input.message,
+          rev.getChange().getId().toString(),
+          rev.getPatchSet().getPatchSetId());
+    }
+
+    @Override
+    public Description getDescription(
+        RevisionResource resource) {
+      return new Description()
+          .setLabel("Say hello")
+          .setTitle("Say hello in different languages");
+    }
+  }
+====
+
+`UiAction` must be bound in a plugin module:
+
+====
+  public class Module extends AbstractModule {
+    @Override
+    protected void configure() {
+      install(new RestApiModule() {
+        @Override
+        protected void configure() {
+          post(REVISION_KIND, "say-hello")
+              .to(HelloWorldAction.class);
+        }
+      });
+    }
+  }
+====
+
+The module above must be declared in pom.xml for Maven driven plugins:
+
+====
+  <manifestEntries>
+    <Gerrit-Module>com.googlesource.gerrit.plugins.cookbook.Module</Gerrit-Module>
+  </manifestEntries>
+====
+
+or in the BUCK configuration file for Buck driven plugins:
+
+====
+  manifest_entries = [
+    'Gerrit-Module: com.googlesource.gerrit.plugins.cookbook.Module',
+  ]
+====
+
+In some use cases more user input must be gathered, for that `UiAction` can be
+combined with the JavaScript API. This would display a small popup near the
+activation button to gather additional input from the user. The JS file is
+typically put in the `static` folder within the plugin's directory:
+
+====
+  Gerrit.install(function(self) {
+    function onSayHello(c) {
+      var f = c.textfield();
+      var t = c.checkbox();
+      var b = c.button('Say hello', {onclick: function(){
+        c.call(
+          {message: f.value, french: t.checked},
+          function(r) {
+            c.hide();
+            window.alert(r);
+            c.refresh();
+          });
+      }});
+      c.popup(c.div(
+        c.prependLabel('Greeting message', f),
+        c.br(),
+        c.label(t, 'french'),
+        c.br(),
+        b));
+      f.focus();
+    }
+    self.onAction('revision', 'say-hello', onSayHello);
+  });
+====
+
+The JS module must be exposed as a `WebUiPlugin` and bound as
+an HTTP Module:
+
+====
+  public class HttpModule extends HttpPluginModule {
+    @Override
+    protected void configureServlets() {
+      DynamicSet.bind(binder(), WebUiPlugin.class)
+          .toInstance(new JavaScriptPlugin("hello.js"));
+    }
+  }
+====
+
+The HTTP module above must be declared in pom.xml for Maven driven plugins:
+
+====
+  <manifestEntries>
+    <Gerrit-HttpModule>com.googlesource.gerrit.plugins.cookbook.HttpModule</Gerrit-HttpModule>
+  </manifestEntries>
+====
+
+or in the BUCK configuration file for Buck driven plugins
+
+====
+  manifest_entries = [
+    'Gerrit-HttpModule: com.googlesource.gerrit.plugins.cookbook.HttpModule',
+  ]
+====
+
+If `UiAction` is annotated with the `@RequiresCapability` annotation, then the
+capability check is done during the `UiAction` gathering, so the plugin author
+doesn't have to set `UiAction.Description.setVisible()` explicitly in this
+case.
+
+The following prerequisities must be met, to satisfy the capability check:
+
+* user is authenticated
+* user is a member of the Administrators group, or
+* user is a member of a group which has the required capability
+
+The `apply` method is called when the button is clicked. If `UiAction` is
+combined with JavaScript API (its own JavaScript function is provided),
+then a popup dialog is normally opened to gather additional user input.
+A new button is placed on the popup dialog to actually send the request.
+
+Every `UiAction` exposes a REST API endpoint. The endpoint from the example above
+can be accessed from any REST client, i. e.:
+
+====
+  curl -X POST -H "Content-Type: application/json" \
+    -d '{message: "François", french: true}' \
+    --digest --user joe:secret \
+    http://host:port/a/changes/1/revisions/1/cookbook~say-hello
+  "Bonjour François from change 1, patch set 1!"
+====
+
 [[http]]
 HTTP Servlets
 -------------
@@ -573,6 +753,12 @@
 Disabled plugins can be re-enabled using the
 link:cmd-plugin-enable.html[plugin enable] command.
 
+SEE ALSO
+--------
+
+* link:js-api.html[JavaScript API]
+* link:dev-rest-api.html[REST API Developers' Notes]
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]