Assume a comment is a robot comment when not found

Currently, when we don't find a comment, we throw IllegalStateException.
Unfortunately, this is not true since in those cases, the comment
that is missing is actually just a robot comment.

This was needed in CommentsUtil#newHumanComment, which always had a bug
that was just not noticed since we usually create new comments using
draft comments. When a comment has a parent robot comment, and
unresolved is unset, we wouldn't find any comment (although the id is
valid, since it belongs to a robot comment).

Also, needed in CommentsUtil#getAllCommentsInCommentThreads, since going
through all the user's comments descendants, we may get into a robot
comment, which we should ignore.

As a follow-up change, in some of those places it's reasonable to also
search for the robot comment, and then throw IllegalStateException if
the comment is neither human nor robot comment.

Change-Id: I06db2d8f3ab0fa01c4962f8d1b081f6811d6982a
This commit is contained in:
Gal Paikin
2020-08-20 12:09:12 +03:00
parent 02a0ecf005
commit b3256183e1
4 changed files with 92 additions and 11 deletions

View File

@@ -37,7 +37,6 @@ import com.google.gerrit.entities.RobotComment;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -132,8 +131,7 @@ public class CommentsUtil {
short side,
String message,
@Nullable Boolean unresolved,
@Nullable String parentUuid)
throws UnprocessableEntityException {
@Nullable String parentUuid) {
if (unresolved == null) {
if (parentUuid == null) {
// Default to false if comment is not descended from another.
@@ -142,10 +140,10 @@ public class CommentsUtil {
// Inherit unresolved value from inReplyTo comment if not specified.
Comment.Key key = new Comment.Key(parentUuid, path, psId.get());
Optional<HumanComment> parent = getPublishedHumanComment(changeNotes, key);
if (!parent.isPresent()) {
throw new UnprocessableEntityException("Invalid parentUuid supplied for comment");
}
unresolved = parent.get().unresolved;
// If the comment was not found, it is descended from a robot comment, or the UUID is
// invalid. Either way, we use the default.
unresolved = parent.map(p -> p.unresolved).orElse(false);
}
}
HumanComment c =
@@ -352,7 +350,7 @@ public class CommentsUtil {
* @param newComments set of all the new comments added on the change by the current user.
* @return set of all comments in the comments thread that received a reply.
*/
public Set<HumanComment> getAllCommentsInCommentThreads(
public Set<HumanComment> getAllHumanCommentsInCommentThreads(
ChangeNotes changeNotes, ImmutableSet<HumanComment> newComments) {
Map<String, HumanComment> uuidToComment =
publishedHumanCommentsByChange(changeNotes).stream()
@@ -369,8 +367,9 @@ public class CommentsUtil {
if (current.parentUuid != null) {
HumanComment parent = uuidToComment.get(current.parentUuid);
if (parent == null) {
throw new IllegalStateException(
String.format("Comment %s not found", current.parentUuid));
// If we can't find the parent within the human comments, the parent must be a robot
// comment and can be ignored.
continue;
}
if (!seen.contains(current.parentUuid)) {
toTraverse.add(parent);

View File

@@ -210,7 +210,7 @@ public class ReplyAttentionSetUpdates {
private void addAllAuthorsOfCommentThreads(
BatchUpdate bu, ChangeNotes changeNotes, ImmutableSet<HumanComment> allNewComments) {
Set<HumanComment> allCommentsInCommentThreads =
commentsUtil.getAllCommentsInCommentThreads(changeNotes, allNewComments);
commentsUtil.getAllHumanCommentsInCommentThreads(changeNotes, allNewComments);
// Copy the set to make it mutable, so that we can delete users that were already added.
Set<Account.Id> possibleUsersToAdd =
new HashSet<>(approvalsUtil.getReviewers(changeNotes).all());

View File

@@ -832,6 +832,25 @@ public class AttentionSetIT extends AbstractDaemonTest {
assertThat(attentionSet.reason()).isEqualTo("Someone else replied on the change");
}
@Test
public void reviewIgnoresRobotCommentsForAttentionSet() throws Exception {
PushOneCommit.Result r = createChange();
requestScopeOperations.setApiUser(user.id());
testCommentHelper.addRobotComment(
r.getChangeId(),
testCommentHelper.createRobotCommentInputWithMandatoryFields(Patch.COMMIT_MSG));
requestScopeOperations.setApiUser(admin.id());
change(r)
.current()
.review(
reviewInReplyToComment(
Iterables.getOnlyElement(
gApi.changes().id(r.getChangeId()).current().robotCommentsAsList())
.id));
assertThat(getAttentionSetUpdatesForUser(r, user)).isEmpty();
}
@Test
public void reviewAddsAllUsersInCommentThread() throws Exception {
PushOneCommit.Result r = createChange();
@@ -869,6 +888,42 @@ public class AttentionSetIT extends AbstractDaemonTest {
assertThat(attentionSet.reason()).isEqualTo("Someone else replied on a comment you posted");
}
@Test
public void reviewAddsAllUsersInCommentThreadWhenOriginalCommentIsARobotComment()
throws Exception {
PushOneCommit.Result result = createChange();
testCommentHelper.addRobotComment(
result.getChangeId(),
testCommentHelper.createRobotCommentInputWithMandatoryFields(Patch.COMMIT_MSG));
requestScopeOperations.setApiUser(user.id());
// Reply to the robot comment.
change(result)
.current()
.review(
reviewInReplyToComment(
Iterables.getOnlyElement(
gApi.changes().id(result.getChangeId()).current().robotCommentsAsList())
.id));
requestScopeOperations.setApiUser(admin.id());
// Reply to the human comment. which was a reply to the robot comment.
change(result)
.current()
.review(
reviewInReplyToComment(
Iterables.getOnlyElement(
gApi.changes().id(result.getChangeId()).current().commentsAsList())
.id));
// The user which replied to the robot comment was added to the attention set.
AttentionSetUpdate attentionSet =
Iterables.getOnlyElement(getAttentionSetUpdatesForUser(result, user));
assertThat(attentionSet.account()).isEqualTo(user.id());
assertThat(attentionSet.operation()).isEqualTo(AttentionSetUpdate.Operation.ADD);
assertThat(attentionSet.reason()).isEqualTo("Someone else replied on a comment you posted");
}
@Test
public void reviewAddsAllUsersInCommentThreadWhenPostedAsDraft() throws Exception {
PushOneCommit.Result r = createChange();

View File

@@ -46,6 +46,7 @@ import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -60,6 +61,7 @@ import com.google.gerrit.server.restapi.change.ChangesCollection;
import com.google.gerrit.server.restapi.change.PostReview;
import com.google.gerrit.testing.FakeEmailSender;
import com.google.gerrit.testing.FakeEmailSender.Message;
import com.google.gerrit.testing.TestCommentHelper;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.sql.Timestamp;
@@ -90,6 +92,7 @@ public class CommentsIT extends AbstractDaemonTest {
@Inject private Provider<PostReview> postReview;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private CommentsUtil commentsUtil;
@Inject private TestCommentHelper testCommentHelper;
private final Integer[] lines = {0, 1};
@@ -1431,6 +1434,30 @@ public class CommentsIT extends AbstractDaemonTest {
assertThat(getChangeSortedComments(id.get())).hasSize(3);
}
@Test
public void canCreateHumanCommentWithRobotCommentAsParentAndUnsetUnresolved() throws Exception {
PushOneCommit.Result result = createChange();
String changeId = result.getChangeId();
String ps1 = result.getCommit().name();
testCommentHelper.addRobotComment(
result.getChangeId(),
testCommentHelper.createRobotCommentInputWithMandatoryFields(FILE_NAME));
RobotCommentInfo robotCommentInfo =
Iterables.getOnlyElement(gApi.changes().id(changeId).current().robotCommentsAsList());
CommentInput comment = newComment(FILE_NAME, "comment 1 reply");
comment.inReplyTo = robotCommentInfo.id;
comment.unresolved = null;
addComments(changeId, ps1, comment);
CommentInfo resultComment = Iterables.getOnlyElement(getPublishedCommentsAsList(changeId));
assertThat(resultComment.inReplyTo).isEqualTo(robotCommentInfo.id);
// Default unresolved is false.
assertThat(resultComment.unresolved).isFalse();
}
private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception {
return getPublishedComments(changeId, revId).values().stream()
.flatMap(List::stream)