LCOV - code coverage report
Current view: top level - server/restapi/change - DeleteVoteOp.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 71 76 93.4 %
Date: 2022-11-19 15:00:39 Functions: 4 4 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2022 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.restapi.change;
      16             : 
      17             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      18             : import static java.util.Objects.requireNonNull;
      19             : 
      20             : import com.google.common.flogger.FluentLogger;
      21             : import com.google.gerrit.entities.Account;
      22             : import com.google.gerrit.entities.Change;
      23             : import com.google.gerrit.entities.LabelTypes;
      24             : import com.google.gerrit.entities.PatchSet;
      25             : import com.google.gerrit.entities.PatchSetApproval;
      26             : import com.google.gerrit.entities.Project;
      27             : import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
      28             : import com.google.gerrit.extensions.restapi.AuthException;
      29             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      30             : import com.google.gerrit.server.ChangeMessagesUtil;
      31             : import com.google.gerrit.server.CurrentUser;
      32             : import com.google.gerrit.server.PatchSetUtil;
      33             : import com.google.gerrit.server.account.AccountState;
      34             : import com.google.gerrit.server.approval.ApprovalsUtil;
      35             : import com.google.gerrit.server.change.NotifyResolver;
      36             : import com.google.gerrit.server.extensions.events.VoteDeleted;
      37             : import com.google.gerrit.server.mail.send.DeleteVoteSender;
      38             : import com.google.gerrit.server.mail.send.MessageIdGenerator;
      39             : import com.google.gerrit.server.mail.send.ReplyToChangeSender;
      40             : import com.google.gerrit.server.permissions.PermissionBackendException;
      41             : import com.google.gerrit.server.project.ProjectCache;
      42             : import com.google.gerrit.server.project.RemoveReviewerControl;
      43             : import com.google.gerrit.server.update.BatchUpdateOp;
      44             : import com.google.gerrit.server.update.ChangeContext;
      45             : import com.google.gerrit.server.update.PostUpdateContext;
      46             : import com.google.gerrit.server.util.AccountTemplateUtil;
      47             : import com.google.gerrit.server.util.LabelVote;
      48             : import com.google.inject.Inject;
      49             : import com.google.inject.assistedinject.Assisted;
      50             : import java.io.IOException;
      51             : import java.util.HashMap;
      52             : import java.util.Map;
      53             : 
      54             : /** Updates the storage to delete vote(s). */
      55             : public class DeleteVoteOp implements BatchUpdateOp {
      56           9 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      57             : 
      58             :   /** Factory to create {@link DeleteVoteOp} instances. */
      59             :   public interface Factory {
      60             :     DeleteVoteOp create(
      61             :         Project.NameKey projectState,
      62             :         AccountState reviewerToDeleteVoteFor,
      63             :         String label,
      64             :         DeleteVoteInput input,
      65             :         boolean enforcePermissions);
      66             :   }
      67             : 
      68             :   private final Project.NameKey projectName;
      69             :   private final AccountState reviewerToDeleteVoteFor;
      70             : 
      71             :   private final ProjectCache projectCache;
      72             :   private final ApprovalsUtil approvalsUtil;
      73             :   private final PatchSetUtil psUtil;
      74             :   private final ChangeMessagesUtil cmUtil;
      75             :   private final VoteDeleted voteDeleted;
      76             :   private final DeleteVoteSender.Factory deleteVoteSenderFactory;
      77             : 
      78             :   private final RemoveReviewerControl removeReviewerControl;
      79             :   private final MessageIdGenerator messageIdGenerator;
      80             : 
      81             :   private final String label;
      82             :   private final DeleteVoteInput input;
      83             :   private final boolean enforcePermissions;
      84             : 
      85             :   private String mailMessage;
      86             :   private Change change;
      87             :   private PatchSet ps;
      88           9 :   private Map<String, Short> newApprovals = new HashMap<>();
      89           9 :   private Map<String, Short> oldApprovals = new HashMap<>();
      90             : 
      91             :   @Inject
      92             :   public DeleteVoteOp(
      93             :       ProjectCache projectCache,
      94             :       ApprovalsUtil approvalsUtil,
      95             :       PatchSetUtil psUtil,
      96             :       ChangeMessagesUtil cmUtil,
      97             :       VoteDeleted voteDeleted,
      98             :       DeleteVoteSender.Factory deleteVoteSenderFactory,
      99             :       RemoveReviewerControl removeReviewerControl,
     100             :       MessageIdGenerator messageIdGenerator,
     101             :       @Assisted Project.NameKey projectName,
     102             :       @Assisted AccountState reviewerToDeleteVoteFor,
     103             :       @Assisted String label,
     104             :       @Assisted DeleteVoteInput input,
     105           9 :       @Assisted boolean enforcePermissions) {
     106           9 :     this.projectCache = projectCache;
     107           9 :     this.approvalsUtil = approvalsUtil;
     108           9 :     this.psUtil = psUtil;
     109           9 :     this.cmUtil = cmUtil;
     110           9 :     this.voteDeleted = voteDeleted;
     111           9 :     this.deleteVoteSenderFactory = deleteVoteSenderFactory;
     112           9 :     this.removeReviewerControl = removeReviewerControl;
     113           9 :     this.messageIdGenerator = messageIdGenerator;
     114             : 
     115           9 :     this.projectName = projectName;
     116           9 :     this.reviewerToDeleteVoteFor = reviewerToDeleteVoteFor;
     117           9 :     this.label = label;
     118           9 :     this.input = input;
     119           9 :     this.enforcePermissions = enforcePermissions;
     120           9 :   }
     121             : 
     122             :   @Override
     123             :   public boolean updateChange(ChangeContext ctx)
     124             :       throws AuthException, ResourceNotFoundException, IOException, PermissionBackendException {
     125           9 :     change = ctx.getChange();
     126           9 :     PatchSet.Id psId = change.currentPatchSetId();
     127           9 :     ps = psUtil.current(ctx.getNotes());
     128             : 
     129           9 :     boolean found = false;
     130           9 :     LabelTypes labelTypes =
     131             :         projectCache
     132           9 :             .get(projectName)
     133           9 :             .orElseThrow(illegalState(projectName))
     134           9 :             .getLabelTypes(ctx.getNotes());
     135             : 
     136           9 :     Account.Id accountId = reviewerToDeleteVoteFor.account().id();
     137             : 
     138           9 :     for (PatchSetApproval a : approvalsUtil.byPatchSetUser(ctx.getNotes(), psId, accountId)) {
     139           9 :       if (!labelTypes.byLabel(a.labelId()).isPresent()) {
     140           0 :         continue; // Ignore undefined labels.
     141           9 :       } else if (!a.label().equals(label)) {
     142             :         // Populate map for non-matching labels, needed by VoteDeleted.
     143           1 :         newApprovals.put(a.label(), a.value());
     144           1 :         continue;
     145           9 :       } else if (enforcePermissions) {
     146             :         // For regular users, check if they are allowed to remove the vote.
     147             :         try {
     148           9 :           removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), a);
     149           1 :         } catch (AuthException e) {
     150           1 :           throw new AuthException("delete vote not permitted", e);
     151           9 :         }
     152             :       }
     153             :       // Set the approval to 0 if vote is being removed.
     154           9 :       newApprovals.put(a.label(), (short) 0);
     155           9 :       found = true;
     156             : 
     157             :       // Set old value, as required by VoteDeleted.
     158           9 :       oldApprovals.put(a.label(), a.value());
     159           9 :       break;
     160             :     }
     161           9 :     if (!found) {
     162           0 :       throw new ResourceNotFoundException();
     163             :     }
     164             : 
     165           9 :     ctx.getUpdate(psId).removeApprovalFor(accountId, label);
     166             : 
     167           9 :     StringBuilder msg = new StringBuilder();
     168           9 :     msg.append("Removed ");
     169           9 :     LabelVote.appendTo(msg, label, requireNonNull(oldApprovals.get(label)));
     170           9 :     msg.append(" by ").append(AccountTemplateUtil.getAccountTemplate(accountId));
     171           9 :     if (input.reason != null) {
     172           1 :       msg.append("\n\n" + input.reason);
     173             :     }
     174           9 :     msg.append("\n");
     175           9 :     mailMessage = cmUtil.setChangeMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_VOTE);
     176           9 :     return true;
     177             :   }
     178             : 
     179             :   @Override
     180             :   public void postUpdate(PostUpdateContext ctx) {
     181           9 :     if (mailMessage == null) {
     182           0 :       return;
     183             :     }
     184             : 
     185           9 :     CurrentUser user = ctx.getUser();
     186             :     try {
     187           9 :       NotifyResolver.Result notify = ctx.getNotify(change.getId());
     188           9 :       if (notify.shouldNotify()) {
     189           9 :         ReplyToChangeSender emailSender =
     190           9 :             deleteVoteSenderFactory.create(ctx.getProject(), change.getId());
     191           9 :         if (user.isIdentifiedUser()) {
     192           9 :           emailSender.setFrom(user.getAccountId());
     193             :         }
     194           9 :         emailSender.setChangeMessage(mailMessage, ctx.getWhen());
     195           9 :         emailSender.setNotify(notify);
     196           9 :         emailSender.setMessageId(
     197           9 :             messageIdGenerator.fromChangeUpdate(ctx.getRepoView(), change.currentPatchSetId()));
     198           9 :         emailSender.send();
     199             :       }
     200           0 :     } catch (Exception e) {
     201           0 :       logger.atSevere().withCause(e).log("Cannot email update for change %s", change.getId());
     202           9 :     }
     203           9 :     voteDeleted.fire(
     204           9 :         ctx.getChangeData(change),
     205             :         ps,
     206             :         reviewerToDeleteVoteFor,
     207             :         newApprovals,
     208             :         oldApprovals,
     209             :         input.notify,
     210             :         mailMessage,
     211           9 :         user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null,
     212           9 :         ctx.getWhen());
     213           9 :   }
     214             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750