Add support for removing project tags through conduit

This allows us to for example remove a project tag after merging a patch.

See our downstream task https://phabricator.wikimedia.org/T95309

Change-Id: I28afe462af3d5733232593ea9cdeca089f845ae4
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java
index dad638c..0459ef0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/PhabricatorItsFacade.java
@@ -104,35 +104,46 @@
       String action = chopped[0];
       switch (action) {
         case "add-project":
-          if (chopped.length == 2) {
-            try {
-              String projectName = chopped[1];
+          assertParameters(action, chopped, 1);
 
-              ProjectInfo projectInfo = conduit.projectQuery(projectName);
-              String projectPhid = projectInfo.getPhid();
+          maniphestEdit(chopped[1], taskId, Conduit.ACTION_PROJECT_ADD);
+          break;
+        case "remove-project":
+          assertParameters(action, chopped, 1);
 
-              Set<String> projectPhids = Sets.newHashSet(projectPhid);
-
-              ManiphestInfo taskInfo = conduit.maniphestInfo(taskId);
-              for (JsonElement jsonElement :
-                taskInfo.getProjectPHIDs().getAsJsonArray()) {
-                projectPhids.add(jsonElement.getAsString());
-              }
-
-              conduit.maniphestEdit(taskId, projectPhids);
-            } catch (ConduitException e) {
-              throw new IOException("Error on conduit", e);
-            }
-          } else {
-            throw new IOException("Action ' + action + ' expects exactly "
-              + "1 parameter but " + (chopped.length - 1) + " given");
-          }
+          maniphestEdit(chopped[1], taskId, Conduit.ACTION_PROJECT_REMOVE);
           break;
         default:
-          throw new IOException("Unknown action ' + action + '");
+          throw new IOException("Unknown action " + action);
       }
     } else {
-      throw new IOException("Could not parse action ' + actionString + '");
+      throw new IOException("Could not parse action " + actionString);
+    }
+  }
+
+  private void assertParameters(String action, String[] params, int length) throws IOException {
+    if (params.length -1 != length) {
+      throw new IOException(String.format("Action %s expects exactly %d parameter(s) but %d given",
+                                          action, length, params.length -1));
+    }
+  }
+
+  private void maniphestEdit(String projectName, int taskId, String actions) throws IOException {
+    try {
+      ProjectInfo projectInfo = conduit.projectQuery(projectName);
+      String projectPhid = projectInfo.getPhid();
+
+      Set<String> projectPhids = Sets.newHashSet(projectPhid);
+
+      ManiphestInfo taskInfo = conduit.maniphestInfo(taskId);
+      for (JsonElement jsonElement :
+        taskInfo.getProjectPHIDs().getAsJsonArray()) {
+        projectPhids.add(jsonElement.getAsString());
+      }
+
+      conduit.maniphestEdit(taskId, projectPhids, actions);
+    } catch (ConduitException e) {
+      throw new IOException("Error on conduit", e);
     }
   }
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java
index fc43b91..7797f8d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/Conduit.java
@@ -47,6 +47,12 @@
  */
 public class Conduit {
 
+  public static final String ACTION_COMMENT = "comment";
+
+  public static final String ACTION_PROJECT_ADD = "projects.add";
+
+  public static final String ACTION_PROJECT_REMOVE = "projects.remove";
+
   private static final Logger log = LoggerFactory.getLogger(Conduit.class);
 
   public static final int CONDUIT_VERSION = 7;
@@ -98,32 +104,35 @@
    * Runs the API's 'maniphest.edit' method
    */
   public ManiphestEdit maniphestEdit(int taskId, String comment) throws ConduitException {
-    return maniphestEdit(taskId, comment, null);
+    return maniphestEdit(taskId, comment, null, ACTION_COMMENT);
   }
 
   /**
    * Runs the API's 'maniphest.edit' method
    */
-  public ManiphestEdit maniphestEdit(int taskId, Iterable<String> projects) throws ConduitException {
-    return maniphestEdit(taskId, null, projects);
+  public ManiphestEdit maniphestEdit(int taskId, Iterable<String> projects, String action) throws ConduitException {
+    return maniphestEdit(taskId, null, projects, action);
   }
 
   /**
    * Runs the API's 'maniphest.edit' method
    */
-  public ManiphestEdit maniphestEdit(int taskId, String comment, Iterable<String> projects) throws ConduitException {
+  public ManiphestEdit maniphestEdit(int taskId, String comment, Iterable<String> projects, String action) throws ConduitException {
     HashMap<String, Object> params = new HashMap<>();
     List<Object> list = new ArrayList<>();
     HashMap<String, Object> params2 = new HashMap<>();
-    if (comment != null) {
-      String comments = "comment";
-      params2.put("type", comments);
+    params2.put("type", action);
+    if(action.equals(ACTION_COMMENT)) {
+      if (comment == null) {
+        throw new IllegalArgumentException("The value of comment (null) is invalid for the action" + action);
+      }
       params2.put("value", comment);
     }
 
-    if (projects != null) {
-      String project = "projects.add";
-      params2.put("type", project);
+    if(action.equals(ACTION_PROJECT_ADD) || action.equals(ACTION_PROJECT_REMOVE)) {
+      if ((action.equals(ACTION_PROJECT_ADD) || action.equals(ACTION_PROJECT_REMOVE)) && projects == null) {
+          throw new IllegalArgumentException("The value of projects (null) is invalid for the action " + action);
+      }
       params2.put("value", projects);
     }
 
diff --git a/src/main/resources/Documentation/config-rulebase-plugin-actions.md b/src/main/resources/Documentation/config-rulebase-plugin-actions.md
index 487be43..9fb7bf7 100644
--- a/src/main/resources/Documentation/config-rulebase-plugin-actions.md
+++ b/src/main/resources/Documentation/config-rulebase-plugin-actions.md
@@ -7,6 +7,9 @@
 [`add-project`][action-add-project]
 : adds a project to the task
 
+[`remove-project`][action-remove-project]
+: removes a project from the task
+
 [basic-actions]: config-rulebase-common.html#actions
 
 [action-add-project]: #action-add-project
@@ -21,6 +24,17 @@
 
 adds the project `MyCoolProject` to the task.
 
+[action-remove-project]: #action-remove-project
+### <a name="action-remove-project">Action: remove-project</a>
+
+The `remove-project` action removes a project from the task. The first
+parameter is the project name to remove. So for example
+
+```
+  action = remove-project MyCoolProject
+```
+
+removes the project `MyCoolProject` from the task.
 
 
 [Back to @PLUGIN@ documentation index][index]