Add jobs panels to the change screen

Retrieve verification info and visualize it on the change screen in two
locations:
 1. a panel containing condenced job results under the labels info.
 2. a drop down panel in the header containing more info about job
    job results.

Future plan might be to allow users to show or hide job results info
depending on a multitude of conditions.

Change-Id: Id4980a7a5cf52271c5addfae25a33d49db958043
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/BuildsDropDownPanel.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/BuildsDropDownPanel.java
deleted file mode 100644
index d967ba3..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/BuildsDropDownPanel.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright (C) 2015 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.verifystatus.client;
-
-import com.google.gerrit.client.GerritUiExtensionPoint;
-import com.google.gerrit.client.info.ChangeInfo;
-import com.google.gerrit.client.rpc.NativeMap;
-import com.google.gerrit.plugin.client.Plugin;
-import com.google.gerrit.plugin.client.extension.Panel;
-import com.google.gerrit.plugin.client.rpc.RestApi;
-import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.InlineHyperlink;
-import com.google.gwt.user.client.ui.InlineLabel;
-
-/**
- * Extension for change screen that displays a status in the header bar.
- */
-public class BuildsDropDownPanel extends FlowPanel {
-  static class Factory implements Panel.EntryPoint {
-    @Override
-    public void onLoad(Panel panel) {
-    }
-  }
-
-  BuildsDropDownPanel(final Panel panel) {
-    ChangeInfo change =
-        panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
-    new RestApi("config")
-      .id("server")
-      .view(Plugin.get().getPluginName(), "verifications")
-      .get(new AsyncCallback<NativeMap<VerificationInfo>>() {
-        @Override
-        public void onSuccess(NativeMap<VerificationInfo> map) {
-          map.copyKeysIntoChildren("job");
-          // TODO only rendern when not empty
-          panel.setWidget(new BuildsDropDownPanel());
-        }
-
-        @Override
-        public void onFailure(Throwable caught) {
-          // never invoked
-        }
-      });
-  }
-
-  BuildsDropDownPanel() {
-    Grid g = new Grid(3, 4);
-    g.addStyleName("infoBlock");
-    CellFormatter fmt = g.getCellFormatter();
-
-    g.setText(0, 0, "State");
-    fmt.addStyleName(0, 0, "header");
-    g.setText(0, 1, "PS");
-    fmt.addStyleName(0, 1, "header");
-    g.setText(0, 2, "Date");
-    fmt.addStyleName(0, 2, "header");
-    g.setText(0, 3, "Log");
-    fmt.addStyleName(0, 3, "header");
-
-    HorizontalPanel p = new HorizontalPanel();
-    p.add(new Image(VerifyStatusPlugin.RESOURCES.greenCheck()));
-    p.add(new InlineLabel("OK"));
-    g.setWidget(1, 0, p);
-    g.setWidget(1, 1, new InlineLabel("2"));
-    g.setWidget(1, 2, new InlineLabel("2015-07-09 11:06:13"));
-    g.setWidget(1, 3, new InlineHyperlink("Build Log", "TODO"));
-
-    p = new HorizontalPanel();
-    p.add(new Image(VerifyStatusPlugin.RESOURCES.redNot()));
-    p.add(new InlineLabel("FAILED"));
-    g.setWidget(2, 0, p);
-    g.setWidget(2, 1, new InlineLabel("1"));
-    g.setWidget(2, 2, new InlineLabel("2015-07-09 09:17:28"));
-    g.setWidget(2, 3, new InlineHyperlink("Build Log", "TODO"));
-
-    fmt.addStyleName(0, 0, "topmost");
-    fmt.addStyleName(0, 1, "topmost");
-    fmt.addStyleName(0, 2, "topmost");
-    fmt.addStyleName(0, 3, "topmost");
-
-    add(new PopDownButton("Builds", g));
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsDropDownPanel.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsDropDownPanel.java
new file mode 100644
index 0000000..e7fff26
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsDropDownPanel.java
@@ -0,0 +1,100 @@
+// Copyright (C) 2015 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.verifystatus.client;
+
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.plugin.client.FormatUtil;
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineHyperlink;
+import com.google.gwt.user.client.ui.InlineLabel;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Extension for change screen that displays a status in the header bar.
+ */
+public class JobsDropDownPanel extends FlowPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new JobsDropDownPanel(panel));
+    }
+  }
+
+  JobsDropDownPanel(Panel panel) {
+    ChangeInfo change =
+        panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
+    RevisionInfo rev =
+        panel.getObject(GerritUiExtensionPoint.Key.REVISION_INFO).cast();
+    new RestApi("changes").id(change.id()).view("revisions").id(rev.id())
+        .view(Plugin.get().getPluginName(), "verifications")
+        .get(new AsyncCallback<NativeMap<VerificationInfo>>() {
+          @Override
+          public void onSuccess(NativeMap<VerificationInfo> result) {
+            if (!result.isEmpty()) {
+              Map<String, VerificationInfo> jobs = new TreeMap<>();
+              for (String key : result.keySet()) {
+                jobs.put(key, result.get(key));
+              }
+              display(jobs);
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+  }
+
+  private void display(Map<String, VerificationInfo> jobs) {
+    int row = 0;
+    int column = 5;
+    Grid grid = new Grid(row, column);
+    for (Map.Entry<String, VerificationInfo> job : jobs.entrySet()) {
+      grid.insertRow(row);
+      HorizontalPanel p = new HorizontalPanel();
+      short vote = job.getValue().value();
+      if (vote > 0) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.greenCheck()));
+      } else if (vote < 0) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.redNot()));
+      }
+      p.add(new InlineHyperlink(job.getKey(), job.getValue().url()));
+      p.add(new InlineLabel("(" + job.getValue().duration() + ")"));
+      if (job.getValue().abstain()) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.info()));
+      }
+      grid.setWidget(row, 1, p);
+      grid.setWidget(row, 2, new InlineLabel(job.getValue().category()));
+      grid.setWidget(row, 3, new InlineLabel(job.getValue().reporter()));
+      grid.setWidget(row, 4,
+          new InlineLabel(FormatUtil.shortFormat(job.getValue().granted())));
+      row++;
+    }
+    add(new PopDownButton("Jobs", grid));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsPanel.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsPanel.java
new file mode 100644
index 0000000..143daab
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/JobsPanel.java
@@ -0,0 +1,95 @@
+// 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.
+
+package com.googlesource.gerrit.plugins.verifystatus.client;
+
+import com.google.gerrit.client.GerritUiExtensionPoint;
+import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gerrit.plugin.client.extension.Panel;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Image;
+import com.google.gwt.user.client.ui.InlineHyperlink;
+import com.google.gwt.user.client.ui.InlineLabel;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Extension for change screen that displays a status below the label info.
+ */
+public class JobsPanel extends FlowPanel {
+  static class Factory implements Panel.EntryPoint {
+    @Override
+    public void onLoad(Panel panel) {
+      panel.setWidget(new JobsPanel(panel));
+    }
+  }
+
+  JobsPanel(Panel panel) {
+    ChangeInfo change =
+        panel.getObject(GerritUiExtensionPoint.Key.CHANGE_INFO).cast();
+    RevisionInfo rev =
+        panel.getObject(GerritUiExtensionPoint.Key.REVISION_INFO).cast();
+    new RestApi("changes").id(change.id()).view("revisions").id(rev.id())
+        .view(Plugin.get().getPluginName(), "verifications")
+        .get(new AsyncCallback<NativeMap<VerificationInfo>>() {
+          @Override
+          public void onSuccess(NativeMap<VerificationInfo> result) {
+            if (!result.isEmpty()) {
+              Map<String, VerificationInfo> jobs = new TreeMap<>();
+              for (String key : result.keySet()) {
+                jobs.put(key, result.get(key));
+              }
+              display(jobs);
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable caught) {
+            // never invoked
+          }
+        });
+  }
+
+  private void display(Map<String, VerificationInfo> jobs) {
+    int row = 0;
+    int column = 1;
+    Grid grid = new Grid(row, column);
+    for (Map.Entry<String, VerificationInfo> job : jobs.entrySet()) {
+      grid.insertRow(row);
+      HorizontalPanel p = new HorizontalPanel();
+      short vote = job.getValue().value();
+      if (vote > 0) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.greenCheck()));
+      } else if (vote < 0) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.redNot()));
+      }
+      p.add(new InlineHyperlink(job.getKey(), job.getValue().url()));
+      p.add(new InlineLabel(" (" + job.getValue().duration() + ")"));
+      if (job.getValue().abstain()) {
+        p.add(new Image(VerifyStatusPlugin.RESOURCES.info()));
+      }
+      grid.setWidget(row, 0, p);
+      row++;
+    }
+    add(grid);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerifyStatusPlugin.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerifyStatusPlugin.java
index 753dc40..466e860 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerifyStatusPlugin.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerifyStatusPlugin.java
@@ -27,6 +27,9 @@
   public void onPluginLoad() {
     Plugin.get().panel(
         GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_POP_DOWNS,
-        new BuildsDropDownPanel.Factory());
+        new JobsDropDownPanel.Factory());
+    Plugin.get().panel(
+        GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        new JobsPanel.Factory());
   }
 }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 9e01ba0..e8d9aee 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,4 +1,44 @@
-This plugin allows CI system to report build and test results back to Gerrit.
-The reports are stored per patchset and are saved onto an external database.
-This plugin provides a set of SSH and REST APIs to automate the reporting
-process.   The per patchset results are displayed on the Gerrit UI.
+<link href="../com/googlesource/gerrit/plugins/verifystatus/public/verifystatus.css" rel="stylesheet"></link>
+
+The @PLUGIN@ plugin allows CI system to report build and test results back to
+Gerrit. The reports are stored per patchset and are saved onto an external
+database.  Included with this plugin are a set of SSH and REST APIs to automate
+the reporting of test results.  This plugin will also handle displaying of the
+job results on the Gerrit UI.
+
+
+### <a id="workflow"></a>
+### `Workflow`
+
+A typical workflow for @PLUGIN@ plugin:
+
+1. CI system triggers on a new patchset.
+2. CI system executes build jobs.
+3. CI system reports build job results with @PLUGIN@ [ssh command](cmd-save.html)
+or [rest-api](rest-api-changes.md).
+4. CI system reports a combined `Verfiied` vote based on the results of each job
+using the review [ssh command](../../../Documentation/cmd-review.html) or
+[rest-api](../../../Documentation/rest-api-changes.html#set-review).
+5. Users can view per patch job results on Gerrit UI or retrieve the results
+using the @PLUGIN@ rest api.
+
+
+### <a id="change-screen"></a>
+### `Change Screen`
+Visualized based on the [job results](#job-results) info
+
+![PreferencesScreenshot](images/job_results.png)
+
+
+
+### <a id="job-results"></a>
+### `Job Results`
+
+Job result scores represent the results from the executed build.  These
+scores are independent of the Gerrit label (i.e. `Verified`) score. The
+reporter scores each build job and then (if given permission) scores the
+combined Verified vote.
+
+The light bulb is an indicator that a job has abstained from voting
+(or is a non-voting job).  Abstaining typically indicates that a job's
+score may not factor into determining the combined vote.
diff --git a/src/main/resources/Documentation/cmd-save.md b/src/main/resources/Documentation/cmd-save.md
index cc13ec3..5a31ce4 100644
--- a/src/main/resources/Documentation/cmd-save.md
+++ b/src/main/resources/Documentation/cmd-save.md
@@ -71,8 +71,10 @@
 EXAMPLES
 --------
 
-Verified gate-horizon-pep8 test with vote=+1 on the change with commit 14a95001c.
-__Notice__ two levels of quoting are required, one for the local shell, and
+Report results for 'gate-horizon-pep8' job with score=+1 on the patchset with
+commit 14a95001c.  Results can be updated by posting with the same job name.
+
+*__Notice__ two levels of quoting are required, one for the local shell, and
 another for the argument parser inside the Gerrit server.
 
 
diff --git a/src/main/resources/Documentation/images/job_results.png b/src/main/resources/Documentation/images/job_results.png
new file mode 100644
index 0000000..d066398
--- /dev/null
+++ b/src/main/resources/Documentation/images/job_results.png
Binary files differ
diff --git a/src/main/resources/Documentation/rest-api-changes.md b/src/main/resources/Documentation/rest-api-changes.md
index 44917de..aedb0a8 100644
--- a/src/main/resources/Documentation/rest-api-changes.md
+++ b/src/main/resources/Documentation/rest-api-changes.md
@@ -84,7 +84,8 @@
 
 __POST__ /changes/{change-id}/revisions/{revision-id}/@PLUGIN@~verifications
 
-Posts a verification result to a patchset.
+Posts a verification result to a patchset.  Results can be updated by posting
+with the same job name.
 
 The verification must be provided in the request body as a
 [VerifyInput](#verify-input) entity.