diff options
author | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2018-12-27 15:04:51 +0200 |
---|---|---|
committer | Jukka Jokiniva <jukka.jokiniva@qt.io> | 2019-01-11 06:05:39 +0000 |
commit | 5a99d4343201c361e93071469fdc65b3ec43355e (patch) | |
tree | 40c5f2be9cb2e458dadc66a0e0949e47ff76aa29 | |
parent | 01ff86b7df744ada936443e0b2329ace41ebb692 (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>
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; + } + +} |