Add an event property value to an ITS field

This allows to add a value to an ITS field able to hold more than one value.
For example, it can be used to populate fixed versions field on git tag creation.

Change-Id: Ibd93af414fe6f8e7de7d9651bdd21eade1b7b1e5
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraClient.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraClient.java
index 2c8128c..cf14882 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraClient.java
@@ -25,6 +25,7 @@
 import com.googlesource.gerrit.plugins.its.base.its.InvalidTransitionException;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraComment;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraIssue;
+import com.googlesource.gerrit.plugins.its.jira.restapi.JiraIssueUpdate;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraProject;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraRestApi;
 import com.googlesource.gerrit.plugins.its.jira.restapi.JiraRestApiProvider;
@@ -110,6 +111,19 @@
     log.debug("Version {} created on project {}", version, projectKey);
   }
 
+  public void addValueToField(
+      JiraItsServerInfo server, String issueKey, String value, String fieldId) throws IOException {
+    if (!issueExists(server, issueKey)) {
+      log.error("Issue {} does not exist", issueKey);
+      return;
+    }
+
+    log.debug("Trying to add value {} to field {} for issue {}", value, fieldId, issueKey);
+    JiraIssueUpdate edition = JiraIssueUpdate.builder().appendUpdate(fieldId, "add", value).build();
+    apiBuilder.getIssue(server).doPut(issueKey, gson.toJson(edition), HTTP_NO_CONTENT);
+    log.debug("Value {} added to field {} for issue {}", value, fieldId, issueKey);
+  }
+
   /**
    * @param issueKey Jira Issue key
    * @param transition JiraTransition.Item to perform
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacade.java
index 1827a28..3fc1c82 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacade.java
@@ -69,6 +69,17 @@
   }
 
   @Override
+  public void addValueToField(String issueKey, String value, String fieldId) throws IOException {
+    execute(
+        () -> {
+          log.debug("Adding value {} to field {} on issue {}", value, fieldId, issueKey);
+          jiraClient.addValueToField(itsServerInfo, issueKey, value, fieldId);
+          // No value to return
+          return null;
+        });
+  }
+
+  @Override
   public void performAction(String issueKey, String actionName) throws IOException {
     execute(
         () -> {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdate.java b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdate.java
new file mode 100644
index 0000000..bf9ea15
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdate.java
@@ -0,0 +1,62 @@
+// 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.
+
+package com.googlesource.gerrit.plugins.its.jira.restapi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JiraIssueUpdate {
+
+  private final Map<String, List<Map<String, Object>>> update;
+
+  private JiraIssueUpdate(Map<String, List<Map<String, Object>>> update) {
+    this.update = Collections.unmodifiableMap(update);
+  }
+
+  public Map<String, List<Map<String, Object>>> getUpdate() {
+    return update;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private final Map<String, List<Map<String, Object>>> update;
+
+    private Builder() {
+      this.update = new HashMap<>();
+    }
+
+    public Builder appendUpdate(String fieldId, String operation, String value) {
+      Object valueToPut = value;
+      if ("fixVersions".equals(fieldId)) {
+        valueToPut = Collections.singletonMap("name", value);
+      }
+
+      this.update
+          .computeIfAbsent(fieldId, key -> new ArrayList<>())
+          .add(Collections.singletonMap(operation, valueToPut));
+      return this;
+    }
+
+    public JiraIssueUpdate build() {
+      return new JiraIssueUpdate(update);
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacadeTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacadeTest.java
index a7e9768..793f09f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacadeTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/jira/JiraItsFacadeTest.java
@@ -32,6 +32,8 @@
   private static final String COMMENT = "comment";
   private static final String ISSUE_KEY = "issueKey";
   private static final String PROJECT_KEY = "projectKey";
+  private static final String FIELD_ID = "fieldId";
+  private static final String VALUE = "value";
 
   @Mock private JiraClient jiraClient;
   private JiraItsServerInfo server;
@@ -66,6 +68,13 @@
   }
 
   @Test
+  public void addValueToField() throws IOException {
+    jiraFacade = new JiraItsFacade(jiraClient);
+    jiraFacade.addValueToField(ISSUE_KEY, VALUE, FIELD_ID);
+    verify(jiraClient).addValueToField(server, ISSUE_KEY, VALUE, FIELD_ID);
+  }
+
+  @Test
   public void performAction() throws IOException, InvalidTransitionException {
     jiraFacade = new JiraItsFacade(jiraClient);
     jiraFacade.performAction(ISSUE_KEY, ACTION);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdateTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdateTest.java
new file mode 100644
index 0000000..1ecba22
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/jira/restapi/JiraIssueUpdateTest.java
@@ -0,0 +1,50 @@
+// 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.
+
+package com.googlesource.gerrit.plugins.its.jira.restapi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.json.OutputFormat;
+import com.google.gson.Gson;
+import org.junit.Test;
+
+public class JiraIssueUpdateTest {
+
+  private static final String FIELD_ID = "fieldId";
+  private static final String OPERATION = "operation";
+  private static final String VALUE = "value";
+
+  @Test
+  public void testSerialization() {
+    JiraIssueUpdate issueUpdate =
+        JiraIssueUpdate.builder().appendUpdate(FIELD_ID, OPERATION, VALUE).build();
+
+    assertThat(newGson().toJson(issueUpdate))
+        .isEqualTo("{\"update\":{\"fieldId\":[{\"operation\":\"value\"}]}}");
+  }
+
+  @Test
+  public void testSerializationForFixVersions() {
+    JiraIssueUpdate issueUpdate =
+        JiraIssueUpdate.builder().appendUpdate("fixVersions", OPERATION, VALUE).build();
+
+    assertThat(newGson().toJson(issueUpdate))
+        .isEqualTo("{\"update\":{\"fixVersions\":[{\"operation\":{\"name\":\"value\"}}]}}");
+  }
+
+  private Gson newGson() {
+    return OutputFormat.JSON_COMPACT.newGson();
+  }
+}