aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJukka Jokiniva <jukka.jokiniva@qt.io>2018-12-27 15:04:51 +0200
committerJukka Jokiniva <jukka.jokiniva@qt.io>2019-01-11 06:05:39 +0000
commit5a99d4343201c361e93071469fdc65b3ec43355e (patch)
tree40c5f2be9cb2e458dadc66a0e0949e47ff76aa29
parent01ff86b7df744ada936443e0b2329ace41ebb692 (diff)
Fix Abandon in deferred status
Deferred is a closed status so standard abandon doesnt work. New custom UI button added with REST API implementation. Change-Id: Ib7b7b637fff5e8439d04a6fab6d1f279f19ba22a Reviewed-by: Kari Oikarinen <kari.oikarinen@qt.io> Reviewed-by: Paul Wicking <paul.wicking@qt.io> Reviewed-by: Frederik Gladhorn <frederik.gladhorn@qt.io>
-rw-r--r--qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html5
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandon.java118
-rw-r--r--src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java1
-rw-r--r--src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandonIT.java111
4 files changed, 234 insertions, 1 deletions
diff --git a/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html b/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
index fa67064..f077159 100644
--- a/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
+++ b/qt-gerrit-ui-plugin/qt-gerrit-ui-plugin.html
@@ -9,6 +9,10 @@
'use strict';
var BUTTONS = {
+ 'gerrit-plugin-qt-workflow~abandon' : {
+ header: 'Abandon the change?',
+ action_name: 'abandon'
+ },
'gerrit-plugin-qt-workflow~defer' : {
header: 'Defer the change?',
action_name: 'defer'
@@ -118,7 +122,6 @@
break;
}
}
-
if (button_action) {
plugin.popup('qt-gerrit-ui-confirm-dialog').then( (param) => {
plugin.custom_popup_promise = param;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandon.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandon.java
new file mode 100644
index 0000000..b0dafe6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandon.java
@@ -0,0 +1,118 @@
+//
+// Copyright (C) 2018 The Qt Company
+//
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import com.google.common.base.Strings;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.api.changes.AbandonInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+
+
+// Standard Abandon is not allowed if already in a closed state
+// This class handles transition from DEFERRED to ABANDONED
+
+@Singleton
+public class QtAbandon extends RetryingRestModifyView<ChangeResource, AbandonInput, ChangeInfo>
+ implements UiAction<ChangeResource> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Provider<ReviewDb> dbProvider;
+ private final ChangeJson.Factory json;
+ private final PatchSetUtil psUtil;
+ private final QtChangeUpdateOp.Factory qtUpdateFactory;
+
+ @Inject
+ QtAbandon(Provider<ReviewDb> dbProvider,
+ ChangeJson.Factory json,
+ RetryHelper retryHelper,
+ PatchSetUtil psUtil,
+ QtChangeUpdateOp.Factory qtUpdateFactory) {
+ super(retryHelper);
+ this.dbProvider = dbProvider;
+ this.json = json;
+ this.psUtil = psUtil;
+ this.qtUpdateFactory = qtUpdateFactory;
+ }
+
+ @Override
+ protected ChangeInfo applyImpl(BatchUpdate.Factory updateFactory,
+ ChangeResource rsrc,
+ AbandonInput input)
+ throws RestApiException, UpdateException,
+ OrmException, PermissionBackendException,
+ IOException {
+ Change change = rsrc.getChange();
+ logger.atInfo().log("qtcodereview: abandon %s", change);
+
+ // Not allowed to abandon if the current patch set is locked.
+ psUtil.checkPatchSetNotLocked(rsrc.getNotes());
+
+ rsrc.permissions().database(dbProvider).check(ChangePermission.ABANDON);
+
+ if (change.getStatus() != Change.Status.DEFERRED) {
+ logger.atSevere().log("qtcodereview: qtabandon: change %s status wrong %s", change, change.getStatus());
+ throw new ResourceConflictException("change is " + ChangeUtil.status(change));
+ }
+
+ QtChangeUpdateOp op = qtUpdateFactory.create(Change.Status.ABANDONED,
+ "Abandoned",
+ input.message,
+ ChangeMessagesUtil.TAG_ABANDON);
+ try (BatchUpdate u = updateFactory.create(dbProvider.get(), change.getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
+ u.addOp(rsrc.getId(), op).execute();
+ }
+
+ logger.atInfo().log("qtcodereview: abandoned %s", change);
+
+ change = op.getChange();
+ return json.noOptions().format(change);
+ }
+
+ @Override
+ public UiAction.Description getDescription(ChangeResource rsrc) {
+ UiAction.Description description = new UiAction.Description()
+ .setLabel("Abandon")
+ .setTitle("Abandon the change")
+ .setVisible(false)
+ .setEnabled(false);
+ Change change = rsrc.getChange();
+ if (change.getStatus() != Change.Status.DEFERRED) {
+ return description;
+ }
+
+ try {
+ if (psUtil.isPatchSetLocked(rsrc.getNotes())) {
+ return description;
+ }
+ } catch (OrmException | IOException e) {
+ logger.atSevere().withCause(e).log("Failed to check if the current patch set of change %s is locked", change.getId());
+ return description;
+ }
+
+ return description.setVisible(rsrc.permissions().testOrFalse(ChangePermission.ABANDON));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
index 76c5aa1..4b478b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/qtcodereview/QtModule.java
@@ -29,6 +29,7 @@ public class QtModule extends FactoryModule {
new RestApiModule() {
@Override
protected void configure() {
+ post(CHANGE_KIND, "abandon").to(QtAbandon.class);
post(CHANGE_KIND, "defer").to(QtDefer.class);
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandonIT.java b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandonIT.java
new file mode 100644
index 0000000..44448f3
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/qtcodereview/QtAbandonIT.java
@@ -0,0 +1,111 @@
+// Copyright (C) 2018 The Qt Company
+
+package com.googlesource.gerrit.plugins.qtcodereview;
+
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.UseSsh;
+
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.AbandonInput;
+import com.google.gerrit.reviewdb.client.Change;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.http.HttpStatus;
+
+@TestPlugin(
+ name = "gerrit-plugin-qt-workflow",
+ sysModule = "com.googlesource.gerrit.plugins.qtcodereview.QtModule",
+ sshModule = "com.googlesource.gerrit.plugins.qtcodereview.QtSshModule"
+)
+
+@UseSsh
+public class QtAbandonIT extends QtCodeReviewIT {
+
+ @Before
+ public void SetDefaultPermissions() throws Exception {
+ grant(project, "refs/heads/master", Permission.ABANDON, false, REGISTERED_USERS);
+ }
+
+ @Test
+ public void singleChange_Defer_QtAbandon() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ QtDefer(c);
+ qtAbandon(c);
+ }
+
+ @Test
+ public void singleChange_Defer_QtAbandon_With_Input_Messsage() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+ approve(c.getChangeId());
+ String changeId = c.getChangeId();
+
+ QtDefer(c);
+
+ AbandonInput abandonInput = new AbandonInput();
+ abandonInput.message = "myabandonednote";
+
+ String url = "/changes/" + changeId + "/gerrit-plugin-qt-workflow~abandon";
+ RestResponse response = userRestSession.post(url, abandonInput);
+ response.assertOK();
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.ABANDONED);
+ }
+
+ @Test
+ public void errorQtAbandon_No_Permission() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+
+ QtDefer(c);
+
+ deny(project, "refs/heads/master", Permission.ABANDON, REGISTERED_USERS);
+ RestResponse response = qtAbandonExpectFail(c, HttpStatus.SC_FORBIDDEN);
+ assertThat(response.getEntityContent()).isEqualTo("abandon not permitted");
+ grant(project, "refs/heads/master", Permission.ABANDON, false, REGISTERED_USERS);
+ }
+
+ @Test
+ public void errorQtAbandon_Wrong_Status() throws Exception {
+ PushOneCommit.Result c = pushCommit("master", "commitmsg1", "file1", "content1");
+
+ RestResponse response = qtAbandonExpectFail(c, HttpStatus.SC_CONFLICT);
+ assertThat(response.getEntityContent()).isEqualTo("change is new");
+ }
+
+ private void qtAbandon(PushOneCommit.Result c) throws Exception {
+ RestResponse response = call_REST_API_QtAbandon(c.getChangeId());
+ response.assertOK();
+
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isEqualTo(Change.Status.ABANDONED);
+ }
+
+ private RestResponse qtAbandonExpectFail(PushOneCommit.Result c,
+ int expectedStatus)
+ throws Exception {
+ RestResponse response = call_REST_API_QtAbandon(c.getChangeId());
+ response.assertStatus(expectedStatus);
+
+ Change change = c.getChange().change();
+ assertThat(change.getStatus()).isNotEqualTo(Change.Status.ABANDONED);
+
+ return response;
+ }
+
+ private RestResponse call_REST_API_QtAbandon(String changeId) throws Exception {
+ String url = "/changes/" + changeId + "/gerrit-plugin-qt-workflow~abandon";
+ RestResponse response = userRestSession.post(url);
+ return response;
+ }
+
+}