Page MenuHomePhabricator
Paste P3635

add-project patch for its-phabricator
ActivePublic

Authored by QChris on Aug 3 2016, 8:57 PM.
Referenced Files
F4334416: add-project patch for its-phabricator
Aug 3 2016, 8:57 PM
Subscribers
None
From b1a272b1dcc15189f8a54b9b37c5ff2b6ecb79dd Mon Sep 17 00:00:00 2001
From: Christian Aistleitner <christian@quelltextlich.at>
Date: Tue, 24 Feb 2015 20:44:11 +0100
Subject: [PATCH] Add add-project action
Change-Id: Ifc687456ba58e0521bf6bcb0cd54fdda6412303a
---
.../its/phabricator/PhabricatorItsFacade.java | 44 ++++-
.../plugins/its/phabricator/conduit/Conduit.java | 50 ++++-
.../phabricator/conduit/results/ProjectInfo.java | 79 ++++++++
.../phabricator/conduit/results/QueryResult.java | 50 +++++
.../config-rulebase-plugin-actions.md | 28 +++
.../its/phabricator/conduit/ConduitTest.java | 203 ++++++++++++++++++++-
6 files changed, 451 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ProjectInfo.java
create mode 100644 src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/QueryResult.java
create mode 100644 src/main/resources/Documentation/config-rulebase-plugin-actions.md
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 3616317..2ccf8c4 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
@@ -16,19 +16,24 @@ package com.googlesource.gerrit.plugins.its.phabricator;
import java.io.IOException;
import java.net.URL;
+import java.util.Set;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gson.JsonElement;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.hooks.its.ItsFacade;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.Conduit;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.ConduitErrorException;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.ConduitException;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestInfo;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ProjectInfo;
public class PhabricatorItsFacade implements ItsFacade {
private static final Logger log = LoggerFactory.getLogger(PhabricatorItsFacade.class);
@@ -101,7 +106,44 @@ public class PhabricatorItsFacade implements ItsFacade {
}
@Override
- public void performAction(final String bugId, final String actionString) {
+ public void performAction(final String taskIdString, final String actionString)
+ throws IOException {
+ int taskId = Integer.parseInt(taskIdString);
+ String chopped[] = actionString.split(" ");
+ if (chopped.length >= 1) {
+ String action = chopped[0];
+ switch (action) {
+ case "add-project":
+ if (chopped.length == 2) {
+ try {
+ String projectName = chopped[1];
+
+ 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.maniphestUpdate(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");
+ }
+ break;
+ default:
+ throw new IOException("Unknown action ' + action + '");
+ }
+ } else {
+ throw new IOException("Could not parse action ' + actionString + '");
+ }
// No custom actions at this point.
//
// Note that you can use hashtag names in comments to associate a task
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 fe42b5b..27b838f 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
@@ -22,6 +22,8 @@ import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitCo
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitPing;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestInfo;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestUpdate;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ProjectInfo;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.QueryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,8 +31,10 @@ import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Map.Entry;
import javax.xml.bind.DatatypeConverter;
@@ -173,13 +177,57 @@ public class Conduit {
* Runs the API's 'maniphest.update' method
*/
public ManiphestUpdate maniphestUpdate(int taskId, String comment) throws ConduitException {
+ return maniphestUpdate(taskId, comment, null);
+ }
+
+ /**
+ * Runs the API's 'maniphest.update' method
+ */
+ public ManiphestUpdate maniphestUpdate(int taskId, Iterable<String> projects) throws ConduitException {
+ return maniphestUpdate(taskId, null, projects);
+ }
+
+ /**
+ * Runs the API's 'maniphest.update' method
+ */
+ public ManiphestUpdate maniphestUpdate(int taskId, String comment, Iterable<String> projects) throws ConduitException {
Map<String, Object> params = new HashMap<String, Object>();
fillInSession(params);
params.put("id", taskId);
- params.put("comments", comment);
+ if (comment != null) {
+ params.put("comments", comment);
+ }
+ if (projects != null) {
+ params.put("projectPHIDs", projects);
+ }
JsonElement callResult = conduitConnection.call("maniphest.update", params);
ManiphestUpdate result = gson.fromJson(callResult, ManiphestUpdate.class);
return result;
}
+
+ /**
+ * Runs the API's 'projectQuery' method to match exactly one project name
+ */
+ public ProjectInfo projectQuery(String name) throws ConduitException {
+ Map<String, Object> params = new HashMap<String, Object>();
+ fillInSession(params);
+ params.put("names", Arrays.asList(name));
+
+ JsonElement callResult = conduitConnection.call("project.query", params);
+ QueryResult queryResult = gson.fromJson(callResult, QueryResult.class);
+ JsonObject queryResultData = queryResult.getData().getAsJsonObject();
+
+ ProjectInfo result = null;
+ for (Entry<String, JsonElement> queryResultEntry:
+ queryResultData.entrySet()) {
+ JsonElement queryResultEntryValue = queryResultEntry.getValue();
+ ProjectInfo queryResultProjectInfo =
+ gson.fromJson(queryResultEntryValue, ProjectInfo.class);
+ if (queryResultProjectInfo.getName().equals(name)) {
+ result = queryResultProjectInfo;
+ }
+ }
+ return result;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ProjectInfo.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ProjectInfo.java
new file mode 100644
index 0000000..a3414c9
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/ProjectInfo.java
@@ -0,0 +1,79 @@
+//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.its.phabricator.conduit.results;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Models the result for API methods returning Project information
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "id":"23",
+ * "phid":"PHID-PROJ-lxmsio4ggx63mhakxhnn",
+ * "name":"QChris-Test-Project",
+ * "profileImagePHID":null,
+ * "icon":"briefcase",
+ * "color":"blue",
+ * "members":["PHID-USER-kem5g5ua7s75ffvlzwgk","PHID-USER-h4n62fq2kt2v3a2qjyqh"],
+ * "slugs":["qchris-test-project"],
+ * "dateCreated":"1413551900",
+ * "dateModified":"1424557030"
+ * }
+ * </pre>
+ */
+public class ProjectInfo {
+ private int id;
+ private String phid;
+ private String name;
+ private String profileImagePHID;
+ private String icon;
+ private String color;
+ private JsonElement members;
+ private JsonElement slugs;
+ private String dateCreated;
+ private String dateModified;
+
+ public int getId() {
+ return id;
+ }
+ public String getPhid() {
+ return phid;
+ }
+ public String getName() {
+ return name;
+ }
+ public String getProfileImagePHID() {
+ return profileImagePHID;
+ }
+ public String getIcon() {
+ return icon;
+ }
+ public String getColor() {
+ return color;
+ }
+ public JsonElement getMembers() {
+ return members;
+ }
+ public JsonElement getSlugs() {
+ return slugs;
+ }
+ public String getDateCreated() {
+ return dateCreated;
+ }
+ public String getDateModified() {
+ return dateModified;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/QueryResult.java b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/QueryResult.java
new file mode 100644
index 0000000..bd0467d
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/results/QueryResult.java
@@ -0,0 +1,50 @@
+//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.its.phabricator.conduit.results;
+
+import com.google.gson.JsonElement;
+
+/**
+ * Models the result for API methods returning a (possible paged) QueryResult
+ * <p/>
+ * JSON looks like:
+ * <pre>
+ * {
+ * "data": { ... },
+ * "slugMap": [],
+ * "cursor": {
+ * "limit": 100,
+ * "after": null,
+ * "before": null
+ * }
+ * }
+ * </pre>
+ */
+public class QueryResult {
+ private JsonElement data;
+ private JsonElement slugMap;
+ private JsonElement cursor;
+
+ public JsonElement getData() {
+ return data;
+ }
+
+ public JsonElement getSlugMap() {
+ return slugMap;
+ }
+
+ public JsonElement getCursor() {
+ return cursor;
+ }
+}
diff --git a/src/main/resources/Documentation/config-rulebase-plugin-actions.md b/src/main/resources/Documentation/config-rulebase-plugin-actions.md
new file mode 100644
index 0000000..487be43
--- /dev/null
+++ b/src/main/resources/Documentation/config-rulebase-plugin-actions.md
@@ -0,0 +1,28 @@
+@PLUGIN@-specific actions
+=========================
+
+In addition to the [basic actions][basic-actions], @PLUGIN@ also
+provides:
+
+[`add-project`][action-add-project]
+: adds a project to the task
+
+[basic-actions]: config-rulebase-common.html#actions
+
+[action-add-project]: #action-add-project
+### <a name="action-add-project">Action: add-project</a>
+
+The `add-project` action adds a project to the task. The first
+parameter is the project name to add. So for example
+
+```
+ action = add-project MyCoolProject
+```
+
+adds the project `MyCoolProject` to the task.
+
+
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java
index 769d7fb..e4240eb 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/its/phabricator/conduit/ConduitTest.java
@@ -24,8 +24,13 @@ import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
@@ -33,6 +38,7 @@ import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitCo
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ConduitPing;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestInfo;
import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ManiphestUpdate;
+import com.googlesource.gerrit.plugins.its.phabricator.conduit.results.ProjectInfo;
import com.googlesource.gerrit.plugins.its.testutil.LoggingMockingTestCase;
@RunWith(PowerMockRunner.class)
@@ -242,7 +248,7 @@ public class ConduitTest extends LoggingMockingTestCase {
assertLogMessageContains("Trying to start new session");
}
- public void testManiphestUpdatePass() throws Exception {
+ public void testManiphestUpdatePassComment() throws Exception {
mockConnection();
resetToStrict(connection);
@@ -282,6 +288,93 @@ public class ConduitTest extends LoggingMockingTestCase {
assertLogMessageContains("Trying to start new session");
}
+ public void testManiphestUpdatePassProjects() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("id", new JsonPrimitive(42));
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.update"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ManiphestUpdate maniphestUpdate = conduit.maniphestUpdate(42,
+ Arrays.asList("foo", "bar"));
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("id"));
+ assertEquals("Task projects are not set", Arrays.asList("foo", "bar"),
+ paramsRelevant.get("projectPHIDs"));
+
+ assertEquals("ManiphestUpdate's id does not match", 42, maniphestUpdate.getId());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testManiphestUpdatePassCommentAndProjects() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("id", new JsonPrimitive(42));
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("maniphest.update"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ManiphestUpdate maniphestUpdate = conduit.maniphestUpdate(42, "baz",
+ Arrays.asList("foo", "bar"));
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ assertEquals("Task id is not set", 42, paramsRelevant.get("id"));
+ assertEquals("Task comment is not set", "baz", paramsRelevant.get("comments"));
+ assertEquals("Task projects are not set", Arrays.asList("foo", "bar"),
+ paramsRelevant.get("projectPHIDs"));
+
+ assertEquals("ManiphestUpdate's id does not match", 42, maniphestUpdate.getId());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
public void testManiphestUpdateFailConnect() throws Exception {
mockConnection();
@@ -393,6 +486,114 @@ public class ConduitTest extends LoggingMockingTestCase {
assertEquals("ManiphestInfo's id does not match", 42, maniphestInfo.getId());
}
+ public void testProjectQueryPass() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject projectInfoJson = new JsonObject();
+ projectInfoJson.addProperty("name", "foo");
+ projectInfoJson.addProperty("phid", "PHID-PROJ-bar");
+
+ JsonObject queryDataJson = new JsonObject();
+ queryDataJson.add("PHID-PROJ-bar", projectInfoJson);
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("data", queryDataJson);
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("project.query"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ProjectInfo projectInfo = conduit.projectQuery("foo");
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ List<String> expectedNames = Arrays.asList("foo");
+ assertEquals("Project name does not match", expectedNames,
+ paramsRelevant.get("names"));
+
+ assertEquals("ProjectInfo's name does not match", "foo", projectInfo.getName());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
+ public void testProjectQueryPassMultipleResults() throws Exception {
+ mockConnection();
+
+ resetToStrict(connection);
+
+ JsonObject retConnect = new JsonObject();
+ retConnect.add("sessionKey", new JsonPrimitive("KeyFoo"));
+
+ Capture<Map<String, Object>> paramsCaptureConnect = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("conduit.connect"), capture(paramsCaptureConnect)))
+ .andReturn(retConnect)
+ .once();
+
+ JsonObject projectInfoJson1 = new JsonObject();
+ projectInfoJson1.addProperty("name", "foo1");
+ projectInfoJson1.addProperty("phid", "PHID-PROJ-bar1");
+
+ JsonObject projectInfoJson2 = new JsonObject();
+ projectInfoJson2.addProperty("name", "foo2");
+ projectInfoJson2.addProperty("phid", "PHID-PROJ-bar2");
+
+ JsonObject projectInfoJson3 = new JsonObject();
+ projectInfoJson3.addProperty("name", "foo3");
+ projectInfoJson3.addProperty("phid", "PHID-PROJ-bar3");
+
+ JsonObject queryDataJson = new JsonObject();
+ queryDataJson.add("PHID-PROJ-bar1", projectInfoJson1);
+ queryDataJson.add("PHID-PROJ-bar2", projectInfoJson2);
+ queryDataJson.add("PHID-PROJ-bar3", projectInfoJson3);
+
+ JsonObject retRelevant = new JsonObject();
+ retRelevant.add("data", queryDataJson);
+
+ Capture<Map<String, Object>> paramsCaptureRelevant = new Capture<Map<String, Object>>();
+
+ expect(connection.call(eq("project.query"), capture(paramsCaptureRelevant)))
+ .andReturn(retRelevant)
+ .once();
+
+ replayMocks();
+
+ Conduit conduit = new Conduit(URL, USERNAME, CERTIFICATE);
+
+ ProjectInfo projectInfo = conduit.projectQuery("foo2");
+
+ Map<String, Object> paramsConnect = paramsCaptureConnect.getValue();
+ assertEquals("Usernames do not match", USERNAME, paramsConnect.get("user"));
+
+ Map<String, Object> paramsRelevant = paramsCaptureRelevant.getValue();
+ List<String> expectedNames = Arrays.asList("foo2");
+ assertEquals("Project name does not match", expectedNames,
+ paramsRelevant.get("names"));
+
+ assertEquals("ProjectInfo's name does not match", "foo2", projectInfo.getName());
+
+ assertLogMessageContains("Trying to start new session");
+ }
+
private void mockConnection() throws Exception {
connection = createMock(ConduitConnection.class);;
expectNew(ConduitConnection.class, URL)
--
1.8.1.5