LCOV - code coverage report
Current view: top level - server/change - AddReviewersOp.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 76 76 100.0 %
Date: 2022-11-19 15:00:39 Functions: 7 7 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.common.base.Preconditions.checkArgument;
      18             : import static com.google.common.base.Preconditions.checkState;
      19             : import static com.google.common.collect.ImmutableList.toImmutableList;
      20             : import static com.google.gerrit.extensions.client.ReviewerState.CC;
      21             : import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
      22             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      23             : import static java.util.Objects.requireNonNull;
      24             : import static java.util.stream.Collectors.toList;
      25             : 
      26             : import com.google.common.collect.ImmutableList;
      27             : import com.google.common.collect.ImmutableSet;
      28             : import com.google.common.collect.Lists;
      29             : import com.google.common.collect.Streams;
      30             : import com.google.gerrit.entities.Account;
      31             : import com.google.gerrit.entities.Address;
      32             : import com.google.gerrit.entities.Change;
      33             : import com.google.gerrit.entities.PatchSetApproval;
      34             : import com.google.gerrit.extensions.client.ReviewerState;
      35             : import com.google.gerrit.extensions.restapi.RestApiException;
      36             : import com.google.gerrit.server.PatchSetUtil;
      37             : import com.google.gerrit.server.account.AccountCache;
      38             : import com.google.gerrit.server.account.AccountState;
      39             : import com.google.gerrit.server.approval.ApprovalsUtil;
      40             : import com.google.gerrit.server.extensions.events.ReviewerAdded;
      41             : import com.google.gerrit.server.notedb.ReviewerStateInternal;
      42             : import com.google.gerrit.server.project.ProjectCache;
      43             : import com.google.gerrit.server.update.ChangeContext;
      44             : import com.google.gerrit.server.update.PostUpdateContext;
      45             : import com.google.inject.Inject;
      46             : import com.google.inject.assistedinject.Assisted;
      47             : import java.io.IOException;
      48             : import java.util.Collection;
      49             : import java.util.List;
      50             : import java.util.Set;
      51             : 
      52             : public class AddReviewersOp extends ReviewerOp {
      53             :   public interface Factory {
      54             : 
      55             :     /**
      56             :      * Create a new op.
      57             :      *
      58             :      * <p>Users may be added by account or by email addresses, as determined by {@code accountIds}
      59             :      * and {@code addresses}. The reviewer state for both accounts and email addresses is determined
      60             :      * by {@code state}.
      61             :      *
      62             :      * @param accountIds account IDs to add.
      63             :      * @param addresses email addresses to add.
      64             :      * @param state resulting reviewer state.
      65             :      * @param forGroup whether this reviewer addition adds accounts for a group
      66             :      * @return batch update operation.
      67             :      */
      68             :     AddReviewersOp create(
      69             :         Set<Account.Id> accountIds,
      70             :         Collection<Address> addresses,
      71             :         ReviewerState state,
      72             :         boolean forGroup);
      73             :   }
      74             : 
      75             :   private final ApprovalsUtil approvalsUtil;
      76             :   private final PatchSetUtil psUtil;
      77             :   private final ReviewerAdded reviewerAdded;
      78             :   private final AccountCache accountCache;
      79             :   private final ProjectCache projectCache;
      80             :   private final ModifyReviewersEmail modifyReviewersEmail;
      81             :   private final Set<Account.Id> accountIds;
      82             :   private final Collection<Address> addresses;
      83             :   private final ReviewerState state;
      84             :   private final boolean forGroup;
      85             : 
      86             :   // Unlike addedCCs, addedReviewers is a PatchSetApproval because the ReviewerResult returned
      87             :   // via the REST API is supposed to include vote information.
      88          47 :   private List<PatchSetApproval> addedReviewers = ImmutableList.of();
      89          47 :   private Collection<Address> addedReviewersByEmail = ImmutableList.of();
      90          47 :   private Collection<Account.Id> addedCCs = ImmutableList.of();
      91          47 :   private Collection<Address> addedCCsByEmail = ImmutableList.of();
      92             : 
      93             :   private Change change;
      94             : 
      95             :   @Inject
      96             :   AddReviewersOp(
      97             :       ApprovalsUtil approvalsUtil,
      98             :       PatchSetUtil psUtil,
      99             :       ReviewerAdded reviewerAdded,
     100             :       AccountCache accountCache,
     101             :       ProjectCache projectCache,
     102             :       ModifyReviewersEmail modifyReviewersEmail,
     103             :       @Assisted Set<Account.Id> accountIds,
     104             :       @Assisted Collection<Address> addresses,
     105             :       @Assisted ReviewerState state,
     106          47 :       @Assisted boolean forGroup) {
     107          47 :     checkArgument(state == REVIEWER || state == CC, "must be %s or %s: %s", REVIEWER, CC, state);
     108          47 :     this.approvalsUtil = approvalsUtil;
     109          47 :     this.psUtil = psUtil;
     110          47 :     this.reviewerAdded = reviewerAdded;
     111          47 :     this.accountCache = accountCache;
     112          47 :     this.projectCache = projectCache;
     113          47 :     this.modifyReviewersEmail = modifyReviewersEmail;
     114             : 
     115          47 :     this.accountIds = accountIds;
     116          47 :     this.addresses = addresses;
     117          47 :     this.state = state;
     118          47 :     this.forGroup = forGroup;
     119          47 :   }
     120             : 
     121             :   @Override
     122             :   public boolean updateChange(ChangeContext ctx) throws RestApiException, IOException {
     123          47 :     change = ctx.getChange();
     124          47 :     if (!accountIds.isEmpty()) {
     125          42 :       if (state == CC) {
     126          29 :         addedCCs =
     127          29 :             approvalsUtil.addCcs(
     128          29 :                 ctx.getNotes(), ctx.getUpdate(change.currentPatchSetId()), accountIds, forGroup);
     129             :       } else {
     130          33 :         addedReviewers =
     131          33 :             approvalsUtil.addReviewers(
     132          33 :                 ctx.getNotes(),
     133          33 :                 ctx.getUpdate(change.currentPatchSetId()),
     134             :                 projectCache
     135          33 :                     .get(change.getProject())
     136          33 :                     .orElseThrow(illegalState(change.getProject()))
     137          33 :                     .getLabelTypes(change.getDest()),
     138             :                 change,
     139             :                 accountIds);
     140             :       }
     141             :     }
     142             : 
     143          47 :     ReviewerStateInternal internalState = ReviewerStateInternal.fromReviewerState(state);
     144             : 
     145             :     // TODO(dborowitz): This behavior should live in ApprovalsUtil or something, like addCcs does.
     146          47 :     ImmutableSet<Address> existing = ctx.getNotes().getReviewersByEmail().byState(internalState);
     147          47 :     ImmutableList<Address> addressesToAdd =
     148          47 :         addresses.stream().filter(a -> !existing.contains(a)).collect(toImmutableList());
     149             : 
     150          47 :     if (state == CC) {
     151          39 :       addedCCsByEmail = addressesToAdd;
     152             :     } else {
     153          33 :       addedReviewersByEmail = addressesToAdd;
     154             :     }
     155          47 :     for (Address a : addressesToAdd) {
     156          10 :       ctx.getUpdate(change.currentPatchSetId()).putReviewerByEmail(a, internalState);
     157          10 :     }
     158             : 
     159          47 :     if (addedCCs.isEmpty() && addedReviewers.isEmpty() && addressesToAdd.isEmpty()) {
     160          33 :       return false;
     161             :     }
     162             : 
     163          42 :     checkAdded();
     164             : 
     165          42 :     if (patchSet == null) {
     166          33 :       patchSet = requireNonNull(psUtil.current(ctx.getNotes()));
     167             :     }
     168          42 :     return true;
     169             :   }
     170             : 
     171             :   private void checkAdded() {
     172             :     // Should only affect either reviewers or CCs, not both. But the logic in updateChange is
     173             :     // complex, so programmer error is conceivable.
     174          42 :     boolean addedAnyReviewers = !addedReviewers.isEmpty() || !addedReviewersByEmail.isEmpty();
     175          42 :     boolean addedAnyCCs = !addedCCs.isEmpty() || !addedCCsByEmail.isEmpty();
     176          42 :     checkState(
     177             :         !(addedAnyReviewers && addedAnyCCs),
     178             :         "should not have added both reviewers and CCs:\n"
     179             :             + "Arguments:\n"
     180             :             + "  accountIds=%s\n"
     181             :             + "  addresses=%s\n"
     182             :             + "Results:\n"
     183             :             + "  addedReviewers=%s\n"
     184             :             + "  addedReviewersByEmail=%s\n"
     185             :             + "  addedCCs=%s\n"
     186             :             + "  addedCCsByEmail=%s",
     187             :         accountIds,
     188             :         addresses,
     189             :         addedReviewers,
     190             :         addedReviewersByEmail,
     191             :         addedCCs,
     192             :         addedCCsByEmail);
     193          42 :   }
     194             : 
     195             :   @Override
     196             :   public void postUpdate(PostUpdateContext ctx) throws Exception {
     197          47 :     opResult =
     198          47 :         Result.builder()
     199          47 :             .setAddedReviewers(addedReviewers)
     200          47 :             .setAddedReviewersByEmail(addedReviewersByEmail)
     201          47 :             .setAddedCCs(addedCCs)
     202          47 :             .setAddedCCsByEmail(addedCCsByEmail)
     203          47 :             .build();
     204          47 :     if (sendEmail) {
     205          31 :       modifyReviewersEmail.emailReviewersAsync(
     206          31 :           ctx.getUser().asIdentifiedUser(),
     207             :           change,
     208          31 :           Lists.transform(addedReviewers, PatchSetApproval::accountId),
     209             :           addedCCs,
     210          31 :           ImmutableSet.of(),
     211             :           addedReviewersByEmail,
     212             :           addedCCsByEmail,
     213          31 :           ImmutableSet.of(),
     214          31 :           ctx.getNotify(change.getId()));
     215             :     }
     216          47 :     if (!addedReviewers.isEmpty()) {
     217          33 :       List<AccountState> reviewers =
     218          33 :           addedReviewers.stream()
     219          33 :               .map(r -> accountCache.get(r.accountId()))
     220          33 :               .flatMap(Streams::stream)
     221          33 :               .collect(toList());
     222          33 :       eventSender =
     223             :           () ->
     224          33 :               reviewerAdded.fire(
     225          33 :                   ctx.getChangeData(change), patchSet, reviewers, ctx.getAccount(), ctx.getWhen());
     226          33 :       if (sendEvent) {
     227          33 :         sendEvent();
     228             :       }
     229             :     }
     230          47 :   }
     231             : }

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