Merge "Add ability to modify workInProgress to PostReview"
This commit is contained in:
@@ -3603,7 +3603,7 @@ describing the related changes.
|
|||||||
--
|
--
|
||||||
|
|
||||||
Sets a review on a revision, optionally also publishing draft comments, setting
|
Sets a review on a revision, optionally also publishing draft comments, setting
|
||||||
labels, and adding reviewers or CCs.
|
labels, adding reviewers or CCs, and modifying the work in progress property.
|
||||||
|
|
||||||
The review must be provided in the request body as a
|
The review must be provided in the request body as a
|
||||||
link:#review-input[ReviewInput] entity.
|
link:#review-input[ReviewInput] entity.
|
||||||
@@ -6713,6 +6713,12 @@ have been granted `labelAs-NAME` permission for all keys of labels.
|
|||||||
|`reviewers` |optional|
|
|`reviewers` |optional|
|
||||||
A list of link:rest-api-changes.html#reviewer-input[ReviewerInput]
|
A list of link:rest-api-changes.html#reviewer-input[ReviewerInput]
|
||||||
representing reviewers that should be added to the change.
|
representing reviewers that should be added to the change.
|
||||||
|
|`ready` |optional|
|
||||||
|
If true, and if the change is work in progress, then start review.
|
||||||
|
It is an error for both `ready` and `work_in_progress` to be true.
|
||||||
|
|`work_in_progress` |optional|
|
||||||
|
If true, mark the change as work in progress. It is an error for both
|
||||||
|
`ready` and `work_in_progress` to be true.
|
||||||
|============================
|
|============================
|
||||||
|
|
||||||
[[review-result]]
|
[[review-result]]
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ import com.google.gerrit.reviewdb.client.Project;
|
|||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.change.ChangeResource;
|
import com.google.gerrit.server.change.ChangeResource;
|
||||||
|
import com.google.gerrit.server.change.PostReview;
|
||||||
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
||||||
import com.google.gerrit.server.git.ChangeMessageModifier;
|
import com.google.gerrit.server.git.ChangeMessageModifier;
|
||||||
import com.google.gerrit.server.git.ProjectConfig;
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
@@ -501,6 +502,70 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_READY);
|
assertThat(Iterables.getLast(info.messages).tag).contains(ChangeMessagesUtil.TAG_SET_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewAndStartReview() throws Exception {
|
||||||
|
PushOneCommit.Result r = createWorkInProgressChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
assertThat(r.getChange().change().isWorkInProgress()).isTrue();
|
||||||
|
|
||||||
|
ReviewInput in = ReviewInput.noScore().setWorkInProgress(false);
|
||||||
|
gApi.changes().id(r.getChangeId()).revision("current").review(in);
|
||||||
|
|
||||||
|
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
|
||||||
|
assertThat(info.workInProgress).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewAndMoveToWorkInProgress() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
assertThat(r.getChange().change().isWorkInProgress()).isFalse();
|
||||||
|
|
||||||
|
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
|
||||||
|
gApi.changes().id(r.getChangeId()).revision("current").review(in);
|
||||||
|
|
||||||
|
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
|
||||||
|
assertThat(info.workInProgress).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewAndSetWorkInProgressAndAddReviewerAndVote() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
assertThat(r.getChange().change().isWorkInProgress()).isFalse();
|
||||||
|
|
||||||
|
ReviewInput in =
|
||||||
|
ReviewInput.approve().reviewer(user.email).label("Code-Review", 1).setWorkInProgress(true);
|
||||||
|
gApi.changes().id(r.getChangeId()).revision("current").review(in);
|
||||||
|
|
||||||
|
ChangeInfo info = gApi.changes().id(r.getChangeId()).get();
|
||||||
|
assertThat(info.workInProgress).isTrue();
|
||||||
|
assertThat(info.reviewers.get(REVIEWER).stream().map(ai -> ai._accountId).collect(toList()))
|
||||||
|
.containsExactly(admin.id.get(), user.id.get());
|
||||||
|
assertThat(info.labels.get("Code-Review").recommended._accountId).isEqualTo(admin.id.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewWithWorkInProgressAndReadyReturnsError() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
ReviewInput in = ReviewInput.noScore();
|
||||||
|
in.ready = true;
|
||||||
|
in.workInProgress = true;
|
||||||
|
ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
|
||||||
|
assertThat(result.error).isEqualTo(PostReview.ERROR_WIP_READY_MUTUALLY_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void reviewWithWorkInProgressByNonOwnerReturnsError() throws Exception {
|
||||||
|
PushOneCommit.Result r = createChange();
|
||||||
|
r.assertOkStatus();
|
||||||
|
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
|
||||||
|
setApiUser(user);
|
||||||
|
ReviewResult result = gApi.changes().id(r.getChangeId()).revision("current").review(in);
|
||||||
|
assertThat(result.error).isEqualTo(PostReview.ERROR_ONLY_OWNER_CAN_MODIFY_WORK_IN_PROGRESS);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAmbiguous() throws Exception {
|
public void getAmbiguous() throws Exception {
|
||||||
PushOneCommit.Result r1 = createChange();
|
PushOneCommit.Result r1 = createChange();
|
||||||
|
|||||||
@@ -825,6 +825,44 @@ public class ChangeNotificationsIT extends AbstractNotificationTest {
|
|||||||
.noOneElse();
|
.noOneElse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noCommentAndSetWorkInProgress() throws Exception {
|
||||||
|
StagedChange sc = stageReviewableChange();
|
||||||
|
ReviewInput in = ReviewInput.noScore().setWorkInProgress(true);
|
||||||
|
gApi.changes().id(sc.changeId).revision("current").review(in);
|
||||||
|
assertThat(sender).notSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commentAndSetWorkInProgress() throws Exception {
|
||||||
|
StagedChange sc = stageReviewableChange();
|
||||||
|
ReviewInput in = ReviewInput.noScore().message("ok").setWorkInProgress(true);
|
||||||
|
gApi.changes().id(sc.changeId).revision("current").review(in);
|
||||||
|
assertThat(sender)
|
||||||
|
.sent("comment", sc)
|
||||||
|
.cc(sc.reviewer, sc.ccer)
|
||||||
|
.cc(sc.reviewerByEmail, sc.ccerByEmail)
|
||||||
|
.bcc(sc.starrer)
|
||||||
|
.bcc(ALL_COMMENTS)
|
||||||
|
.noOneElse();
|
||||||
|
assertThat(sender).notSent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void commentOnWipChangeAndStartReview() throws Exception {
|
||||||
|
StagedChange sc = stageWipChange();
|
||||||
|
ReviewInput in = ReviewInput.noScore().message("ok").setWorkInProgress(false);
|
||||||
|
gApi.changes().id(sc.changeId).revision("current").review(in);
|
||||||
|
assertThat(sender)
|
||||||
|
.sent("comment", sc)
|
||||||
|
.cc(sc.reviewer, sc.ccer)
|
||||||
|
.cc(sc.reviewerByEmail, sc.ccerByEmail)
|
||||||
|
.bcc(sc.starrer)
|
||||||
|
.bcc(ALL_COMMENTS)
|
||||||
|
.noOneElse();
|
||||||
|
assertThat(sender).notSent();
|
||||||
|
}
|
||||||
|
|
||||||
private void review(TestAccount account, String changeId, EmailStrategy strategy)
|
private void review(TestAccount account, String changeId, EmailStrategy strategy)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
review(account, changeId, strategy, null);
|
review(account, changeId, strategy, null);
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ public class ReviewInput {
|
|||||||
/** Reviewers that should be added to this change. */
|
/** Reviewers that should be added to this change. */
|
||||||
public List<AddReviewerInput> reviewers;
|
public List<AddReviewerInput> reviewers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true mark the change as work in progress. It is an error for both {@link #workInProgress}
|
||||||
|
* and {@link #ready} to be true.
|
||||||
|
*/
|
||||||
|
public boolean workInProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true mark the change as ready for review. It is an error for both {@link #workInProgress}
|
||||||
|
* and {@link #ready} to be true.
|
||||||
|
*/
|
||||||
|
public boolean ready;
|
||||||
|
|
||||||
public enum DraftHandling {
|
public enum DraftHandling {
|
||||||
/** Delete pending drafts on this revision only. */
|
/** Delete pending drafts on this revision only. */
|
||||||
DELETE,
|
DELETE,
|
||||||
@@ -141,6 +153,18 @@ public class ReviewInput {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReviewInput setWorkInProgress(boolean workInProgress) {
|
||||||
|
this.workInProgress = workInProgress;
|
||||||
|
ready = !workInProgress;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReviewInput setReady(boolean ready) {
|
||||||
|
this.ready = ready;
|
||||||
|
workInProgress = !ready;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public static ReviewInput recommend() {
|
public static ReviewInput recommend() {
|
||||||
return new ReviewInput().label("Code-Review", 1);
|
return new ReviewInput().label("Code-Review", 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,4 +30,7 @@ public class ReviewResult {
|
|||||||
* additions were requested.
|
* additions were requested.
|
||||||
*/
|
*/
|
||||||
@Nullable public Map<String, AddReviewerResult> reviewers;
|
@Nullable public Map<String, AddReviewerResult> reviewers;
|
||||||
|
|
||||||
|
/** Error message for non-200 responses. */
|
||||||
|
@Nullable public String error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ import org.eclipse.jgit.lib.Config;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class PostReview
|
public class PostReview
|
||||||
extends RetryingRestModifyView<RevisionResource, ReviewInput, Response<ReviewResult>> {
|
extends RetryingRestModifyView<RevisionResource, ReviewInput, Response<ReviewResult>> {
|
||||||
|
public static final String ERROR_ADDING_REVIEWER = "error adding reviewer";
|
||||||
|
public static final String ERROR_ONLY_OWNER_CAN_MODIFY_WORK_IN_PROGRESS =
|
||||||
|
"only change owner can specify work_in_progress or ready";
|
||||||
|
public static final String ERROR_WIP_READY_MUTUALLY_EXCLUSIVE =
|
||||||
|
"work_in_progress and ready are mutually exclusive";
|
||||||
|
|
||||||
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
|
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
|
||||||
private static final int DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
|
private static final int DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
|
||||||
|
|
||||||
@@ -150,6 +156,7 @@ public class PostReview
|
|||||||
private final NotesMigration migration;
|
private final NotesMigration migration;
|
||||||
private final NotifyUtil notifyUtil;
|
private final NotifyUtil notifyUtil;
|
||||||
private final Config gerritConfig;
|
private final Config gerritConfig;
|
||||||
|
private final WorkInProgressOp.Factory workInProgressOpFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PostReview(
|
PostReview(
|
||||||
@@ -168,7 +175,8 @@ public class PostReview
|
|||||||
PostReviewers postReviewers,
|
PostReviewers postReviewers,
|
||||||
NotesMigration migration,
|
NotesMigration migration,
|
||||||
NotifyUtil notifyUtil,
|
NotifyUtil notifyUtil,
|
||||||
@GerritServerConfig Config gerritConfig) {
|
@GerritServerConfig Config gerritConfig,
|
||||||
|
WorkInProgressOp.Factory workInProgressOpFactory) {
|
||||||
super(retryHelper);
|
super(retryHelper);
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.changes = changes;
|
this.changes = changes;
|
||||||
@@ -185,6 +193,7 @@ public class PostReview
|
|||||||
this.migration = migration;
|
this.migration = migration;
|
||||||
this.notifyUtil = notifyUtil;
|
this.notifyUtil = notifyUtil;
|
||||||
this.gerritConfig = gerritConfig;
|
this.gerritConfig = gerritConfig;
|
||||||
|
this.workInProgressOpFactory = workInProgressOpFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -259,6 +268,7 @@ public class PostReview
|
|||||||
ReviewResult output = new ReviewResult();
|
ReviewResult output = new ReviewResult();
|
||||||
output.reviewers = reviewerJsonResults;
|
output.reviewers = reviewerJsonResults;
|
||||||
if (hasError || confirm) {
|
if (hasError || confirm) {
|
||||||
|
output.error = ERROR_ADDING_REVIEWER;
|
||||||
return Response.withStatusCode(SC_BAD_REQUEST, output);
|
return Response.withStatusCode(SC_BAD_REQUEST, output);
|
||||||
}
|
}
|
||||||
output.labels = input.labels;
|
output.labels = input.labels;
|
||||||
@@ -310,9 +320,30 @@ public class PostReview
|
|||||||
bu.addOp(revision.getChange().getId(), selfAddition.op);
|
bu.addOp(revision.getChange().getId(), selfAddition.op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add WorkInProgressOp if requested.
|
||||||
|
if (input.ready || input.workInProgress) {
|
||||||
|
if (input.ready && input.workInProgress) {
|
||||||
|
output.error = ERROR_WIP_READY_MUTUALLY_EXCLUSIVE;
|
||||||
|
return Response.withStatusCode(SC_BAD_REQUEST, output);
|
||||||
|
}
|
||||||
|
if (!revision.getChange().getOwner().equals(revision.getUser().getAccountId())) {
|
||||||
|
output.error = ERROR_ONLY_OWNER_CAN_MODIFY_WORK_IN_PROGRESS;
|
||||||
|
return Response.withStatusCode(SC_BAD_REQUEST, output);
|
||||||
|
}
|
||||||
|
// Suppress notifications in WorkInProgressOp, we'll take care of
|
||||||
|
// them in this endpoint.
|
||||||
|
WorkInProgressOp.Input wipIn = new WorkInProgressOp.Input();
|
||||||
|
wipIn.notify = NotifyHandling.NONE;
|
||||||
|
bu.addOp(
|
||||||
|
revision.getChange().getId(),
|
||||||
|
workInProgressOpFactory.create(input.workInProgress, wipIn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the review op.
|
||||||
bu.addOp(
|
bu.addOp(
|
||||||
revision.getChange().getId(),
|
revision.getChange().getId(),
|
||||||
new Op(revision.getPatchSet().getId(), input, accountsToNotify));
|
new Op(revision.getPatchSet().getId(), input, accountsToNotify));
|
||||||
|
|
||||||
bu.execute();
|
bu.execute();
|
||||||
|
|
||||||
for (PostReviewers.Addition reviewerResult : reviewerResults) {
|
for (PostReviewers.Addition reviewerResult : reviewerResults) {
|
||||||
@@ -326,12 +357,20 @@ public class PostReview
|
|||||||
}
|
}
|
||||||
|
|
||||||
private NotifyHandling defaultNotify(Change c, ReviewInput in) {
|
private NotifyHandling defaultNotify(Change c, ReviewInput in) {
|
||||||
if (ChangeMessagesUtil.isAutogenerated(in.tag)) {
|
boolean workInProgress = c.isWorkInProgress();
|
||||||
// Autogenerated comments default to lower notify levels.
|
if (in.workInProgress) {
|
||||||
return c.isWorkInProgress() ? NotifyHandling.OWNER : NotifyHandling.OWNER_REVIEWERS;
|
workInProgress = true;
|
||||||
|
}
|
||||||
|
if (in.ready) {
|
||||||
|
workInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c.isWorkInProgress() && !c.hasReviewStarted()) {
|
if (ChangeMessagesUtil.isAutogenerated(in.tag)) {
|
||||||
|
// Autogenerated comments default to lower notify levels.
|
||||||
|
return workInProgress ? NotifyHandling.OWNER : NotifyHandling.OWNER_REVIEWERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workInProgress && !c.hasReviewStarted()) {
|
||||||
// If review hasn't started we want to minimize recipients, no matter who
|
// If review hasn't started we want to minimize recipients, no matter who
|
||||||
// the author is.
|
// the author is.
|
||||||
return NotifyHandling.OWNER;
|
return NotifyHandling.OWNER;
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.change;
|
package com.google.gerrit.server.change;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableListMultimap;
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
|
import com.google.gerrit.common.Nullable;
|
||||||
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
import com.google.gerrit.extensions.api.changes.NotifyHandling;
|
||||||
import com.google.gerrit.reviewdb.client.Change;
|
import com.google.gerrit.reviewdb.client.Change;
|
||||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
import com.google.gerrit.reviewdb.client.ChangeMessage;
|
||||||
@@ -35,7 +37,9 @@ import com.google.inject.assistedinject.Assisted;
|
|||||||
/* Set work in progress or ready for review state on a change */
|
/* Set work in progress or ready for review state on a change */
|
||||||
public class WorkInProgressOp implements BatchUpdateOp {
|
public class WorkInProgressOp implements BatchUpdateOp {
|
||||||
public static class Input {
|
public static class Input {
|
||||||
String message;
|
@Nullable String message;
|
||||||
|
|
||||||
|
@Nullable NotifyHandling notify;
|
||||||
|
|
||||||
public Input() {}
|
public Input() {}
|
||||||
|
|
||||||
@@ -53,6 +57,7 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
private final PatchSetUtil psUtil;
|
private final PatchSetUtil psUtil;
|
||||||
private final boolean workInProgress;
|
private final boolean workInProgress;
|
||||||
private final Input in;
|
private final Input in;
|
||||||
|
private final NotifyHandling notify;
|
||||||
|
|
||||||
private Change change;
|
private Change change;
|
||||||
private ChangeNotes notes;
|
private ChangeNotes notes;
|
||||||
@@ -71,6 +76,9 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
this.psUtil = psUtil;
|
this.psUtil = psUtil;
|
||||||
this.workInProgress = workInProgress;
|
this.workInProgress = workInProgress;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
|
notify =
|
||||||
|
MoreObjects.firstNonNull(
|
||||||
|
in.notify, workInProgress ? NotifyHandling.NONE : NotifyHandling.ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -113,12 +121,12 @@ public class WorkInProgressOp implements BatchUpdateOp {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postUpdate(Context ctx) {
|
public void postUpdate(Context ctx) {
|
||||||
if (workInProgress) {
|
if (workInProgress || notify.ordinal() < NotifyHandling.OWNER_REVIEWERS.ordinal()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
email
|
email
|
||||||
.create(
|
.create(
|
||||||
NotifyHandling.ALL,
|
notify,
|
||||||
ImmutableListMultimap.of(),
|
ImmutableListMultimap.of(),
|
||||||
notes,
|
notes,
|
||||||
ps,
|
ps,
|
||||||
|
|||||||
Reference in New Issue
Block a user