LCOV - code coverage report
Current view: top level - server/change - DeleteReviewerOp.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 107 110 97.3 %
Date: 2022-11-19 15:00:39 Functions: 9 9 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2017 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.change;
      16             : 
      17             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      18             : 
      19             : import com.google.common.collect.Iterables;
      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.LabelType;
      24             : import com.google.gerrit.entities.LabelTypes;
      25             : import com.google.gerrit.entities.PatchSetApproval;
      26             : import com.google.gerrit.entities.Project;
      27             : import com.google.gerrit.exceptions.EmailException;
      28             : import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
      29             : import com.google.gerrit.extensions.api.changes.NotifyHandling;
      30             : import com.google.gerrit.extensions.restapi.AuthException;
      31             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      32             : import com.google.gerrit.server.ChangeMessagesUtil;
      33             : import com.google.gerrit.server.IdentifiedUser;
      34             : import com.google.gerrit.server.PatchSetUtil;
      35             : import com.google.gerrit.server.account.AccountCache;
      36             : import com.google.gerrit.server.account.AccountState;
      37             : import com.google.gerrit.server.approval.ApprovalsUtil;
      38             : import com.google.gerrit.server.extensions.events.ReviewerDeleted;
      39             : import com.google.gerrit.server.mail.send.DeleteReviewerSender;
      40             : import com.google.gerrit.server.mail.send.MessageIdGenerator;
      41             : import com.google.gerrit.server.notedb.ChangeUpdate;
      42             : import com.google.gerrit.server.notedb.ReviewerStateInternal;
      43             : import com.google.gerrit.server.permissions.PermissionBackendException;
      44             : import com.google.gerrit.server.project.ProjectCache;
      45             : import com.google.gerrit.server.project.RemoveReviewerControl;
      46             : import com.google.gerrit.server.update.ChangeContext;
      47             : import com.google.gerrit.server.update.PostUpdateContext;
      48             : import com.google.gerrit.server.update.RepoView;
      49             : import com.google.gerrit.server.util.AccountTemplateUtil;
      50             : import com.google.inject.Inject;
      51             : import com.google.inject.Provider;
      52             : import com.google.inject.assistedinject.Assisted;
      53             : import java.io.IOException;
      54             : import java.sql.Timestamp;
      55             : import java.util.Collections;
      56             : import java.util.HashMap;
      57             : import java.util.Map;
      58             : 
      59             : public class DeleteReviewerOp extends ReviewerOp {
      60          16 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      61             : 
      62             :   public interface Factory {
      63             :     DeleteReviewerOp create(Account reviewerAccount, DeleteReviewerInput input);
      64             :   }
      65             : 
      66             :   private final ApprovalsUtil approvalsUtil;
      67             :   private final PatchSetUtil psUtil;
      68             :   private final ChangeMessagesUtil cmUtil;
      69             :   private final ReviewerDeleted reviewerDeleted;
      70             :   private final Provider<IdentifiedUser> user;
      71             :   private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
      72             :   private final RemoveReviewerControl removeReviewerControl;
      73             :   private final ProjectCache projectCache;
      74             :   private final MessageIdGenerator messageIdGenerator;
      75             :   private final AccountCache accountCache;
      76             : 
      77             :   private final Account reviewer;
      78             :   private final DeleteReviewerInput input;
      79             : 
      80             :   String mailMessage;
      81             :   Change currChange;
      82          16 :   Map<String, Short> newApprovals = new HashMap<>();
      83          16 :   Map<String, Short> oldApprovals = new HashMap<>();
      84             : 
      85             :   @Inject
      86             :   DeleteReviewerOp(
      87             :       ApprovalsUtil approvalsUtil,
      88             :       PatchSetUtil psUtil,
      89             :       ChangeMessagesUtil cmUtil,
      90             :       ReviewerDeleted reviewerDeleted,
      91             :       Provider<IdentifiedUser> user,
      92             :       DeleteReviewerSender.Factory deleteReviewerSenderFactory,
      93             :       RemoveReviewerControl removeReviewerControl,
      94             :       ProjectCache projectCache,
      95             :       MessageIdGenerator messageIdGenerator,
      96             :       AccountCache accountCache,
      97             :       @Assisted Account reviewerAccount,
      98          16 :       @Assisted DeleteReviewerInput input) {
      99          16 :     this.approvalsUtil = approvalsUtil;
     100          16 :     this.psUtil = psUtil;
     101          16 :     this.cmUtil = cmUtil;
     102          16 :     this.reviewerDeleted = reviewerDeleted;
     103          16 :     this.user = user;
     104          16 :     this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
     105          16 :     this.removeReviewerControl = removeReviewerControl;
     106          16 :     this.projectCache = projectCache;
     107          16 :     this.messageIdGenerator = messageIdGenerator;
     108          16 :     this.accountCache = accountCache;
     109          16 :     this.reviewer = reviewerAccount;
     110          16 :     this.input = input;
     111          16 :   }
     112             : 
     113             :   @Override
     114             :   public boolean updateChange(ChangeContext ctx)
     115             :       throws AuthException, ResourceNotFoundException, PermissionBackendException, IOException {
     116          16 :     Account.Id reviewerId = reviewer.id();
     117             :     // Check of removing this reviewer (even if there is no vote processed by the loop below) is OK
     118          16 :     removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), reviewerId);
     119             : 
     120          16 :     if (!approvalsUtil.getReviewers(ctx.getNotes()).all().contains(reviewerId)) {
     121           1 :       throw new ResourceNotFoundException(
     122           1 :           String.format(
     123             :               "Reviewer %s doesn't exist in the change, hence can't delete it",
     124           1 :               reviewer.getName()));
     125             :     }
     126          16 :     currChange = ctx.getChange();
     127          16 :     setPatchSet(psUtil.current(ctx.getNotes()));
     128             : 
     129          16 :     LabelTypes labelTypes =
     130             :         projectCache
     131          16 :             .get(ctx.getProject())
     132          16 :             .orElseThrow(illegalState(ctx.getProject()))
     133          16 :             .getLabelTypes(ctx.getNotes());
     134             :     // removing a reviewer will remove all her votes
     135          16 :     for (LabelType lt : labelTypes.getLabelTypes()) {
     136          16 :       newApprovals.put(lt.getName(), (short) 0);
     137          16 :     }
     138             :     String ccOrReviewer =
     139             :         approvalsUtil
     140          16 :                 .getReviewers(ctx.getNotes())
     141          16 :                 .byState(ReviewerStateInternal.CC)
     142          16 :                 .contains(reviewerId)
     143           8 :             ? "cc"
     144          16 :             : "reviewer";
     145          16 :     StringBuilder msg = new StringBuilder();
     146          16 :     msg.append(
     147          16 :         String.format(
     148          16 :             "Removed %s %s", ccOrReviewer, AccountTemplateUtil.getAccountTemplate(reviewer.id())));
     149          16 :     StringBuilder removedVotesMsg = new StringBuilder();
     150          16 :     removedVotesMsg.append(" with the following votes:\n\n");
     151          16 :     boolean votesRemoved = false;
     152          16 :     for (PatchSetApproval a : approvals(ctx, reviewerId)) {
     153             :       // Check if removing this vote is OK
     154           4 :       removeReviewerControl.checkRemoveReviewer(ctx.getNotes(), ctx.getUser(), a);
     155           4 :       if (a.patchSetId().equals(patchSet.id()) && a.value() != 0) {
     156           4 :         oldApprovals.put(a.label(), a.value());
     157           4 :         removedVotesMsg
     158           4 :             .append("* ")
     159           4 :             .append(a.label())
     160           4 :             .append(formatLabelValue(a.value()))
     161           4 :             .append(" by ")
     162           4 :             .append(AccountTemplateUtil.getAccountTemplate(a.accountId()))
     163           4 :             .append("\n");
     164           4 :         votesRemoved = true;
     165             :       }
     166           4 :     }
     167             : 
     168          16 :     if (votesRemoved) {
     169           4 :       msg.append(removedVotesMsg);
     170             :     } else {
     171          15 :       msg.append(".");
     172             :     }
     173          16 :     ChangeUpdate update = ctx.getUpdate(patchSet.id());
     174          16 :     update.removeReviewer(reviewerId);
     175             : 
     176          16 :     mailMessage =
     177          16 :         cmUtil.setChangeMessage(ctx, msg.toString(), ChangeMessagesUtil.TAG_DELETE_REVIEWER);
     178          16 :     return true;
     179             :   }
     180             : 
     181             :   @Override
     182             :   public void postUpdate(PostUpdateContext ctx) {
     183          16 :     opResult = Result.builder().setDeletedReviewer(reviewer.id()).build();
     184             : 
     185          16 :     NotifyResolver.Result notify = ctx.getNotify(currChange.getId());
     186          16 :     if (sendEmail) {
     187          15 :       if (input.notify == null
     188          15 :           && currChange.isWorkInProgress()
     189           6 :           && !oldApprovals.isEmpty()
     190           1 :           && notify.handling().compareTo(NotifyHandling.OWNER) < 0) {
     191             :         // Override NotifyHandling from the context to notify owner if votes were removed on a WIP
     192             :         // change.
     193           1 :         notify = notify.withHandling(NotifyHandling.OWNER);
     194             :       }
     195             :       try {
     196          15 :         if (notify.shouldNotify()) {
     197          11 :           emailReviewers(
     198          11 :               ctx.getProject(),
     199             :               currChange,
     200             :               mailMessage,
     201          11 :               Timestamp.from(ctx.getWhen()),
     202             :               notify,
     203          11 :               ctx.getRepoView());
     204             :         }
     205           0 :       } catch (Exception err) {
     206           0 :         logger.atSevere().withCause(err).log(
     207           0 :             "Cannot email update for change %s", currChange.getId());
     208          15 :       }
     209             :     }
     210             : 
     211          16 :     NotifyHandling notifyHandling = notify.handling();
     212          16 :     eventSender =
     213             :         () ->
     214          16 :             reviewerDeleted.fire(
     215          16 :                 ctx.getChangeData(currChange),
     216             :                 patchSet,
     217          16 :                 accountCache.get(reviewer.id()).orElse(AccountState.forAccount(reviewer)),
     218          16 :                 ctx.getAccount(),
     219             :                 mailMessage,
     220             :                 newApprovals,
     221             :                 oldApprovals,
     222             :                 notifyHandling,
     223          16 :                 ctx.getWhen());
     224          16 :     if (sendEvent) {
     225          15 :       sendEvent();
     226             :     }
     227          16 :   }
     228             : 
     229             :   private Iterable<PatchSetApproval> approvals(ChangeContext ctx, Account.Id accountId) {
     230             :     Iterable<PatchSetApproval> approvals;
     231          16 :     approvals = ctx.getNotes().getApprovals().all().values();
     232          16 :     return Iterables.filter(approvals, psa -> accountId.equals(psa.accountId()));
     233             :   }
     234             : 
     235             :   private String formatLabelValue(short value) {
     236           4 :     if (value > 0) {
     237           4 :       return "+" + value;
     238             :     }
     239           1 :     return Short.toString(value);
     240             :   }
     241             : 
     242             :   private void emailReviewers(
     243             :       Project.NameKey projectName,
     244             :       Change change,
     245             :       String mailMessage,
     246             :       Timestamp timestamp,
     247             :       NotifyResolver.Result notify,
     248             :       RepoView repoView)
     249             :       throws EmailException {
     250          11 :     Account.Id userId = user.get().getAccountId();
     251          11 :     if (userId.equals(reviewer.id())) {
     252             :       // The user knows they removed themselves, don't bother emailing them.
     253           5 :       return;
     254             :     }
     255          10 :     DeleteReviewerSender emailSender =
     256          10 :         deleteReviewerSenderFactory.create(projectName, change.getId());
     257          10 :     emailSender.setFrom(userId);
     258          10 :     emailSender.addReviewers(Collections.singleton(reviewer.id()));
     259          10 :     emailSender.setChangeMessage(mailMessage, timestamp.toInstant());
     260          10 :     emailSender.setNotify(notify);
     261          10 :     emailSender.setMessageId(
     262          10 :         messageIdGenerator.fromChangeUpdate(repoView, change.currentPatchSetId()));
     263          10 :     emailSender.send();
     264          10 :   }
     265             : }

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