Add a flag to ignore attention set updates on vote deletion

This is helpful especially for bots that want to delete votes without
changing the attention set.

Google-bug-id: b/205824597
Change-Id: Iae9deb9841381004590ab39b714f7b4699cd773c
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 58a3724..64aa6e0 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -7111,6 +7111,9 @@
 Additional information about whom to notify about the update as a map
 of link:user-notify.html#recipient-types[recipient type] to
 link:#notify-info[NotifyInfo] entity.
+|`ignore_automatic_attention_set_rules`|optional|
+If set to true, ignore all automatic attention set rules described in the
+link:#attention-set[attention set]. When not set, the default is false.
 |=============================
 
 [[description-input]]
diff --git a/java/com/google/gerrit/extensions/api/changes/DeleteVoteInput.java b/java/com/google/gerrit/extensions/api/changes/DeleteVoteInput.java
index ee10a1d..8432c8f 100644
--- a/java/com/google/gerrit/extensions/api/changes/DeleteVoteInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/DeleteVoteInput.java
@@ -25,4 +25,10 @@
   public NotifyHandling notify = NotifyHandling.ALL;
 
   public Map<RecipientType, NotifyInfo> notifyDetails;
+
+  /**
+   * Users in the attention set will not be added/removed from this endpoint call. Normally, users
+   * are added to the attention set upon deletion of their vote by other users.
+   */
+  public boolean ignoreAutomaticAttentionSetRules;
 }
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index 45d1f5a..4387524 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.approval.ApprovalsUtil;
 import com.google.gerrit.server.change.AddToAttentionSetOp;
+import com.google.gerrit.server.change.AttentionSetUnchangedOp;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.VoteResource;
@@ -146,7 +147,8 @@
               r.getReviewerUser().state(),
               rsrc.getLabel(),
               input));
-      if (!r.getReviewerUser().getAccountId().equals(currentUserProvider.get().getAccountId())) {
+      if (!input.ignoreAutomaticAttentionSetRules
+          && !r.getReviewerUser().getAccountId().equals(currentUserProvider.get().getAccountId())) {
         bu.addOp(
             change.getId(),
             attentionSetOpFactory.create(
@@ -154,6 +156,9 @@
                 /* reason= */ "Their vote was deleted",
                 /* notify= */ false));
       }
+      if (input.ignoreAutomaticAttentionSetRules) {
+        bu.addOp(change.getId(), new AttentionSetUnchangedOp());
+      }
       bu.execute();
     }
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index 4bce5d8..9246442 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.extensions.api.changes.AttentionSetInput;
 import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
+import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
 import com.google.gerrit.extensions.api.changes.HashtagsInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewerInput;
@@ -1941,6 +1942,30 @@
   }
 
   @Test
+  public void deleteVotesDoesNotAffectAttentionSetWhenIgnoreAutomaticRulesIsSet() throws Exception {
+    PushOneCommit.Result r = createChange();
+
+    requestScopeOperations.setApiUser(user.id());
+    recommend(r.getChangeId());
+
+    requestScopeOperations.setApiUser(admin.id());
+
+    DeleteVoteInput deleteVoteInput = new DeleteVoteInput();
+    deleteVoteInput.label = LabelId.CODE_REVIEW;
+
+    // set this to true to not change the attention set.
+    deleteVoteInput.ignoreAutomaticAttentionSetRules = true;
+
+    gApi.changes()
+        .id(r.getChangeId())
+        .current()
+        .reviewer(user.id().toString())
+        .deleteVote(deleteVoteInput);
+
+    assertThat(getAttentionSetUpdatesForUser(r, user)).isEmpty();
+  }
+
+  @Test
   public void deleteVotesOfOthersAddThemToAttentionSet() throws Exception {
     PushOneCommit.Result r = createChange();