Automatically update storyboard task status

Allow its-storyboard to automatically update a storyboard's task
status when a Gerrit change is update. The updated task status
is configurable in actions.config

Change-Id: I55e54adc3b9871bd11f42b9e8cb696cdf925398d
(cherry picked from commit b7e45701b150398f809f733f91e46a34a712f452)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardClient.java b/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardClient.java
index 062c3fd..d28013d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardClient.java
@@ -21,6 +21,7 @@
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
@@ -98,6 +99,26 @@
     }
   }
 
+  // generic method to PUT data with a REST endpoint
+  public void putData(final String url, final String data) throws IOException {
+
+    HttpPut HttpPut = new HttpPut(url);
+    HttpPut.addHeader("Authorization", "Bearer " + password);
+    HttpPut.addHeader("Content-Type", "application/json; charset=utf-8");
+    HttpPut.setEntity(new StringEntity(data, "utf-8"));
+    try (CloseableHttpClient httpclient = HttpClients.createDefault();
+        CloseableHttpResponse response = httpclient.execute(HttpPut)) {
+      log.debug("Executing request " + HttpPut.getRequestLine());
+      int responseCode = response.getStatusLine().getStatusCode();
+      if (responseCode == HttpURLConnection.HTTP_OK) {
+        log.info("Updated " + url + " with " + data);
+      } else {
+        log.error("Failed to post, response: " + responseCode + " ("
+            + response.getStatusLine().getReasonPhrase() + ")");
+      }
+    }
+  }
+
   public String getSysInfo() throws IOException {
     return getData(this.baseUrl + SYS_INFO_ENDPOINT);
   }
@@ -116,6 +137,28 @@
     return getData(this.baseUrl + TASKS_ENDPOINT + "/" + issueId);
   }
 
+  public void setStatus(final String issueId, final String status)
+      throws IOException {
+    log.debug("PUT task with data: ({},{})", issueId, status);
+    final String url = baseUrl + TASKS_ENDPOINT + "/" + issueId;
+    final String json =
+        "{\"task_id\":\"" + issueId + "\",\"status\":\"" + status + "\"}";
+
+    putData(url, json);
+  }
+
+  public String getTaskStatus(final String issueId) throws IOException {
+    String taskJson = getTask(issueId);
+    JsonObject jobj = new Gson().fromJson(taskJson, JsonObject.class);
+    return jobj.get("status").getAsString();
+  }
+
+  public String getTaskNotes(final String issueId) throws IOException {
+    String taskJson = getTask(issueId);
+    JsonObject jobj = new Gson().fromJson(taskJson, JsonObject.class);
+    return jobj.get("link").getAsString();
+  }
+
   public void addComment(final String issueId, final String comment)
       throws IOException {
     int story_id = getStoryId(issueId);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardItsFacade.java
index a5f7202..58fcd83 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/storyboard/StoryboardItsFacade.java
@@ -39,8 +39,8 @@
   public StoryboardItsFacade(@PluginName String pluginName,
       @GerritServerConfig Config cfg) {
     final String url = cfg.getString(pluginName, null, GERRIT_CONFIG_URL);
-    final String password = cfg.getString(pluginName, null,
-            GERRIT_CONFIG_PASSWORD);
+    final String password =
+        cfg.getString(pluginName, null, GERRIT_CONFIG_PASSWORD);
 
     this.client = new StoryboardClient(url, password);
   }
@@ -70,16 +70,29 @@
   @Override
   public void addRelatedLink(final String issueId, final URL relatedUrl,
       String description) throws IOException {
-    addComment(issueId, "Related URL: " + createLinkForWebui(
-        relatedUrl.toExternalForm(), description));
+    addComment(issueId, "Related URL: "
+        + createLinkForWebui(relatedUrl.toExternalForm(), description));
   }
 
   @Override
   public void performAction(final String issueId, final String actionString) {
-    // No custom actions at this point.
-    //
-    // Note that you can use hashtag names in comments to associate a task
-    // with a new project.
+
+    try {
+      String actionName =
+          actionString.substring(0, actionString.indexOf(" ")).toLowerCase();
+      String actionValue =
+          actionString.substring(actionString.indexOf(" ") + 1).toLowerCase();
+      if (actionName.equals("set-status")) {
+        if (!client.getTaskStatus(issueId).toLowerCase().equals(actionValue)) {
+          log.info("Updating task " + issueId + " with status: " + actionValue);
+          client.setStatus(issueId, actionValue);
+        }
+      }
+    } catch (StringIndexOutOfBoundsException e) {
+      log.error("Error: Invalid action: " + actionString);
+    } catch (IOException e) {
+      log.error("Error: Failed to peform action: " + actionString);
+    }
   }
 
   @Override
diff --git a/src/main/resources/Documentation/quick-install-guide.md b/src/main/resources/Documentation/quick-install-guide.md
index c69123a..132fc10 100644
--- a/src/main/resources/Documentation/quick-install-guide.md
+++ b/src/main/resources/Documentation/quick-install-guide.md
@@ -92,9 +92,15 @@
 [rule "comment-on-status-update"]
     event-type = patchset-created,change-abandoned,change-restored,change-merged
     action = add-standard-comment
+# set task status to 'review' when a patch is uploaded or when a change is restored
+[rule "change_restored"]
+    event-type = patchset-created,change-restored
+    action = set-status REVIEW
 ```
 
-More detailed information on actions is found the [rules documentation][rules-doc]
+### <a id="task-status"></a>TaskStatus
+Valid task status: TODO, REVIEW, INPROGRESS, MERGED, and INVALID
+
 
 [its-enable]: #its-enable
 <a name="its-enable">Enable the Plugin</a>
@@ -122,3 +128,8 @@
 [config-common-doc]: config-common.html
 [config-doc]: config.html
 [rules-doc]: config-rulebase-common.html
+
+
+SEE ALSO
+--------
+* More detailed information on actions is found the [rules documentation][rules-doc]