Add new REST API call for querying revision actions
If you have enabled the new option `submitWholeTopic`, we eventually want
to query all changes in the topic if they can be submitted (Code-Review +2,
Verified and the user has permission to submit) to determine if the
submit button is enabled in a ChangeScreen. This query may take some
time, so we don't want to have it in the same query for the whole
change screen, which should render fast.
This commit introduces a new REST API call which allows to query
for the available actions for a given revision id:
changes/<id>/revisions/<id>/actions
By introducing a new ActionJson class we can move all actions related
JSON processing out of ChangeJson eventually which currently
covers actions such as in the change/<id>/detail REST API call.
Change-Id: I58c67944e756f0b130a9c1d89d83f7635bf474a7
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index bd72353..115cee5 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1873,6 +1873,46 @@
Adding query parameter `links` (for example `/changes/.../commit?links`)
returns a link:#commit-info[CommitInfo] with the additional field `web_links`.
+[[get-revision-actions]]
+=== Get Revision Actions
+--
+'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/actions'
+--
+
+Retrieves revision link:#action-info[actions] of the revision of a change.
+
+.Request
+----
+ GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1/actions' HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+
+{
+ "submit": {
+ "method": "POST",
+ "label": "Submit",
+ "title": "Submit patch set 1 into master",
+ "enabled": true
+ },
+ "cherrypick": {
+ "method": "POST",
+ "label": "Cherry Pick",
+ "title": "Cherry pick change to a different branch",
+ "enabled": true
+ }
+}
+----
+
+The response is a flat map of possible revision actions mapped to their
+link:#action-info[ActionInfo].
+
[[get-review]]
=== Get Review
--
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
new file mode 100644
index 0000000..b463a53
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -0,0 +1,137 @@
+// 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.google.gerrit.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class ActionsIT extends AbstractDaemonTest {
+ @ConfigSuite.Config
+ public static Config submitWholeTopicEnabled() {
+ return submitWholeTopicEnabledConfig();
+ }
+
+ @Test
+ public void revisionActionsOneChangePerTopic() throws Exception {
+ String changeId = createChangeWithTopic("foo1").getChangeId();
+ approve(changeId);
+ Map<String, ActionInfo> actions = getActions(changeId);
+ assertThat(actions).containsKey("cherrypick");
+ assertThat(actions).containsKey("submit");
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit all 1 changes of the same topic");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ // no other actions
+ assertThat(actions).hasSize(2);
+ }
+
+ @Test
+ public void revisionActionsTwoChangeChangesInTopic() throws Exception {
+ String changeId = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId);
+ // create another change with the same topic
+ createChangeWithTopic("foo2").getChangeId();
+ Map<String, ActionInfo> actions = getActions(changeId);
+ assertThat(actions).containsKey("cherrypick");
+ assertThat(actions).containsKey("submit");
+ // no other actions:
+ assertThat(actions).hasSize(2);
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isNull();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Other changes in this topic are not ready");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ }
+
+ @Test
+ public void revisionActionsTwoChangeChangesInTopicReady() throws Exception {
+ String changeId = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId);
+ // create another change with the same topic
+ String changeId2 = createChangeWithTopic("foo2").getChangeId();
+ approve(changeId2);
+ Map<String, ActionInfo> actions = getActions(changeId);
+ assertThat(actions).containsKey("cherrypick");
+ assertThat(actions).containsKey("submit");
+ // no other actions:
+ assertThat(actions).hasSize(2);
+ if (isSubmitWholeTopicEnabled()) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit whole topic");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit all 2 changes of the same topic");
+ } else {
+ noSubmitWholeTopicAssertions(actions);
+ }
+ }
+
+ private Map<String, ActionInfo> getActions(String changeId)
+ throws IOException {
+ return newGson().fromJson(
+ adminSession.get("/changes/"
+ + changeId
+ + "/revisions/1/actions").getReader(),
+ new TypeToken<Map<String, ActionInfo>>() {}.getType());
+ }
+
+ private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions) {
+ ActionInfo info = actions.get("submit");
+ assertThat(info.enabled).isTrue();
+ assertThat(info.label).isEqualTo("Submit");
+ assertThat(info.method).isEqualTo("POST");
+ assertThat(info.title).isEqualTo("Submit patch set 1 into master");
+ }
+
+ private PushOneCommit.Result createChangeWithTopic(String topic) throws GitAPIException,
+ IOException {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent());
+ assertThat(topic).isNotEmpty();
+ return push.to(git, "refs/for/master/" + topic);
+ }
+
+ private void approve(String changeId) throws IOException {
+ RestResponse r = adminSession.post(
+ "/changes/" + changeId + "/revisions/current/review",
+ new ReviewInput().label("Code-Review", 2));
+ assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
+ r.consume();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
new file mode 100644
index 0000000..24837aab
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -0,0 +1,54 @@
+// 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.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.common.ActionInfo;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.extensions.webui.UiActions;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.util.Providers;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Singleton
+public class ActionJson {
+ private final Revisions revisions;
+
+ @Inject
+ ActionJson(Revisions revisions) {
+ this.revisions = revisions;
+ }
+
+ public Map<String, ActionInfo> format(RevisionResource rsrc) {
+ return toActionMap(rsrc);
+ }
+
+ private Map<String, ActionInfo> toActionMap(RevisionResource rsrc) {
+ Map<String, ActionInfo> out = new LinkedHashMap<>();
+ if (rsrc.getControl().getCurrentUser().isIdentifiedUser()) {
+ Provider<CurrentUser> userProvider = Providers.of(
+ rsrc.getControl().getCurrentUser());
+ for (UiAction.Description d : UiActions.from(
+ revisions, rsrc, userProvider)) {
+ out.put(d.getId(), new ActionInfo(d));
+ }
+ }
+ return out;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
new file mode 100644
index 0000000..d58c8d2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -0,0 +1,35 @@
+// 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.google.gerrit.server.change;
+
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetRevisionActions implements RestReadView<RevisionResource> {
+ private final ActionJson delegate;
+
+ @Inject
+ GetRevisionActions(ActionJson delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Object apply(RevisionResource rsrc) {
+ return Response.withMustRevalidate(delegate.format(rsrc));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index ac4489e..d0e4c99 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -73,6 +73,7 @@
delete(REVIEWER_KIND).to(DeleteReviewer.class);
child(CHANGE_KIND, "revisions").to(Revisions.class);
+ get(REVISION_KIND, "actions").to(GetRevisionActions.class);
post(REVISION_KIND, "cherrypick").to(CherryPick.class);
get(REVISION_KIND, "commit").to(GetCommit.class);
delete(REVISION_KIND).to(DeleteDraftPatchSet.class);