LCOV - code coverage report
Current view: top level - server/mail/send - ReplacePatchSetSender.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 90 94 95.7 %
Date: 2022-11-19 15:00:39 Functions: 20 20 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2009 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.mail.send;
      16             : 
      17             : import static com.google.common.collect.ImmutableList.toImmutableList;
      18             : 
      19             : import com.google.common.base.Supplier;
      20             : import com.google.common.base.Suppliers;
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.common.flogger.FluentLogger;
      23             : import com.google.gerrit.common.Nullable;
      24             : import com.google.gerrit.entities.Account;
      25             : import com.google.gerrit.entities.Change;
      26             : import com.google.gerrit.entities.NotifyConfig.NotifyType;
      27             : import com.google.gerrit.entities.PatchSetApproval;
      28             : import com.google.gerrit.entities.Project;
      29             : import com.google.gerrit.entities.SubmitRequirement;
      30             : import com.google.gerrit.entities.SubmitRequirementResult;
      31             : import com.google.gerrit.exceptions.EmailException;
      32             : import com.google.gerrit.extensions.api.changes.NotifyHandling;
      33             : import com.google.gerrit.extensions.api.changes.RecipientType;
      34             : import com.google.gerrit.extensions.client.ChangeKind;
      35             : import com.google.gerrit.server.util.LabelVote;
      36             : import com.google.inject.Inject;
      37             : import com.google.inject.assistedinject.Assisted;
      38             : import java.util.ArrayList;
      39             : import java.util.Collection;
      40             : import java.util.HashSet;
      41             : import java.util.List;
      42             : import java.util.Map;
      43             : import java.util.Set;
      44             : import org.eclipse.jgit.lib.ObjectId;
      45             : 
      46             : /** Send notice of new patch sets for reviewers. */
      47             : public class ReplacePatchSetSender extends ReplyToChangeSender {
      48          50 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      49             : 
      50             :   public interface Factory {
      51             :     ReplacePatchSetSender create(
      52             :         Project.NameKey project,
      53             :         Change.Id changeId,
      54             :         ChangeKind changeKind,
      55             :         ObjectId preUpdateMetaId,
      56             :         Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults);
      57             :   }
      58             : 
      59          50 :   private final Set<Account.Id> reviewers = new HashSet<>();
      60          50 :   private final Set<Account.Id> extraCC = new HashSet<>();
      61             :   private final ChangeKind changeKind;
      62          50 :   private final Set<PatchSetApproval> outdatedApprovals = new HashSet<>();
      63             :   private final Supplier<Map<SubmitRequirement, SubmitRequirementResult>>
      64             :       preUpdateSubmitRequirementResultsSupplier;
      65             :   private final Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults;
      66             : 
      67             :   @Inject
      68             :   public ReplacePatchSetSender(
      69             :       EmailArguments args,
      70             :       @Assisted Project.NameKey project,
      71             :       @Assisted Change.Id changeId,
      72             :       @Assisted ChangeKind changeKind,
      73             :       @Assisted ObjectId preUpdateMetaId,
      74             :       @Assisted
      75             :           Map<SubmitRequirement, SubmitRequirementResult> postUpdateSubmitRequirementResults) {
      76          50 :     super(args, "newpatchset", newChangeData(args, project, changeId));
      77          50 :     this.changeKind = changeKind;
      78             : 
      79          50 :     this.preUpdateSubmitRequirementResultsSupplier =
      80          50 :         Suppliers.memoize(
      81             :             () ->
      82             :                 // Triggers an (expensive) evaluation of the submit requirements. This is OK since
      83             :                 // all callers sent this email asynchronously, see EmailNewPatchSet.
      84          50 :                 newChangeData(args, project, changeId, preUpdateMetaId)
      85          50 :                     .submitRequirementsIncludingLegacy());
      86             : 
      87          50 :     this.postUpdateSubmitRequirementResults = postUpdateSubmitRequirementResults;
      88          50 :   }
      89             : 
      90             :   @Override
      91             :   protected boolean shouldSendMessage() {
      92          50 :     if (!isChangeNoLongerSubmittable() && changeKind.isTrivialRebase()) {
      93          17 :       logger.atFine().log(
      94             :           "skip email because new patch set is a trivial rebase that didn't make the change"
      95             :               + " non-submittable");
      96          17 :       return false;
      97             :     }
      98             : 
      99          50 :     return super.shouldSendMessage();
     100             :   }
     101             : 
     102             :   public void addReviewers(Collection<Account.Id> cc) {
     103          50 :     reviewers.addAll(cc);
     104          50 :   }
     105             : 
     106             :   public void addExtraCC(Collection<Account.Id> cc) {
     107          50 :     extraCC.addAll(cc);
     108          50 :   }
     109             : 
     110             :   public void addOutdatedApproval(@Nullable Collection<PatchSetApproval> outdatedApprovals) {
     111          50 :     if (outdatedApprovals != null) {
     112          50 :       this.outdatedApprovals.addAll(outdatedApprovals);
     113             :     }
     114          50 :   }
     115             : 
     116             :   @Override
     117             :   protected void init() throws EmailException {
     118          50 :     super.init();
     119             : 
     120          50 :     if (fromId != null) {
     121             :       // Don't call yourself a reviewer of your own patch set.
     122             :       //
     123          50 :       reviewers.remove(fromId);
     124             :     }
     125          50 :     if (args.settings.sendNewPatchsetEmails) {
     126          50 :       if (notify.handling() == NotifyHandling.ALL
     127           6 :           || notify.handling() == NotifyHandling.OWNER_REVIEWERS) {
     128          50 :         reviewers.stream().forEach(r -> add(RecipientType.TO, r));
     129          50 :         extraCC.stream().forEach(cc -> add(RecipientType.CC, cc));
     130             :       }
     131          50 :       rcptToAuthors(RecipientType.CC);
     132             :     }
     133          50 :     bccStarredBy();
     134          50 :     includeWatchers(NotifyType.NEW_PATCHSETS, !change.isWorkInProgress() && !change.isPrivate());
     135          50 :   }
     136             : 
     137             :   @Override
     138             :   protected void formatChange() throws EmailException {
     139          50 :     appendText(textTemplate("ReplacePatchSet"));
     140          50 :     if (useHtml()) {
     141          50 :       appendHtml(soyHtmlTemplate("ReplacePatchSetHtml"));
     142             :     }
     143          50 :   }
     144             : 
     145             :   @Nullable
     146             :   public ImmutableList<String> getReviewerNames() {
     147          50 :     List<String> names = new ArrayList<>();
     148          50 :     for (Account.Id id : reviewers) {
     149          15 :       if (id.equals(fromId)) {
     150          12 :         continue;
     151             :       }
     152          12 :       names.add(getNameFor(id));
     153          12 :     }
     154          50 :     if (names.isEmpty()) {
     155          49 :       return null;
     156             :     }
     157          12 :     return names.stream().sorted().collect(toImmutableList());
     158             :   }
     159             : 
     160             :   private ImmutableList<String> formatOutdatedApprovals() {
     161          50 :     return outdatedApprovals.stream()
     162          50 :         .map(
     163             :             outdatedApproval ->
     164          11 :                 String.format(
     165             :                     "%s by %s",
     166          11 :                     LabelVote.create(outdatedApproval.label(), outdatedApproval.value()).format(),
     167          11 :                     getNameFor(outdatedApproval.accountId())))
     168          50 :         .sorted()
     169          50 :         .collect(toImmutableList());
     170             :   }
     171             : 
     172             :   @Override
     173             :   protected void setupSoyContext() {
     174          50 :     super.setupSoyContext();
     175          50 :     soyContextEmailData.put("reviewerNames", getReviewerNames());
     176          50 :     soyContextEmailData.put("outdatedApprovals", formatOutdatedApprovals());
     177             : 
     178          50 :     if (isChangeNoLongerSubmittable()) {
     179           9 :       soyContext.put("unsatisfiedSubmitRequirements", formatUnsatisfiedSubmitRequirements());
     180           9 :       soyContext.put(
     181             :           "oldSubmitRequirements",
     182           9 :           formatSubmitRequirments(preUpdateSubmitRequirementResultsSupplier.get()));
     183           9 :       soyContext.put(
     184           9 :           "newSubmitRequirements", formatSubmitRequirments(postUpdateSubmitRequirementResults));
     185             :     }
     186          50 :   }
     187             : 
     188             :   /**
     189             :    * Checks whether the change is no longer submittable.
     190             :    *
     191             :    * @return {@code true} if the change has been submittable before the update and is no longer
     192             :    *     submittable after the update has been applied, otherwise {@code false}
     193             :    */
     194             :   private boolean isChangeNoLongerSubmittable() {
     195          50 :     boolean isSubmittablePreUpdate =
     196          50 :         preUpdateSubmitRequirementResultsSupplier.get().values().stream()
     197          50 :             .allMatch(SubmitRequirementResult::fulfilled);
     198          50 :     logger.atFine().log(
     199             :         "the submitability of change %s before the update is %s",
     200          50 :         change.getId(), isSubmittablePreUpdate);
     201          50 :     if (!isSubmittablePreUpdate) {
     202          49 :       return false;
     203             :     }
     204             : 
     205          13 :     boolean isSubmittablePostUpdate =
     206          13 :         postUpdateSubmitRequirementResults.values().stream()
     207          13 :             .allMatch(SubmitRequirementResult::fulfilled);
     208          13 :     logger.atFine().log(
     209             :         "the submitability of change %s after the update is %s",
     210          13 :         change.getId(), isSubmittablePostUpdate);
     211          13 :     return !isSubmittablePostUpdate;
     212             :   }
     213             : 
     214             :   private ImmutableList<String> formatUnsatisfiedSubmitRequirements() {
     215           9 :     return postUpdateSubmitRequirementResults.entrySet().stream()
     216           9 :         .filter(e -> SubmitRequirementResult.Status.UNSATISFIED.equals(e.getValue().status()))
     217           9 :         .map(Map.Entry::getKey)
     218           9 :         .map(SubmitRequirement::name)
     219           9 :         .sorted()
     220           9 :         .collect(toImmutableList());
     221             :   }
     222             : 
     223             :   private static ImmutableList<String> formatSubmitRequirments(
     224             :       Map<SubmitRequirement, SubmitRequirementResult> submitRequirementResults) {
     225           9 :     return submitRequirementResults.entrySet().stream()
     226           9 :         .map(
     227             :             e -> {
     228           9 :               if (e.getValue().errorMessage().isPresent()) {
     229           0 :                 return String.format(
     230             :                     "%s: %s (%s)",
     231           0 :                     e.getKey().name(),
     232           0 :                     e.getValue().status().name(),
     233           0 :                     e.getValue().errorMessage().get());
     234             :               }
     235           9 :               return String.format("%s: %s", e.getKey().name(), e.getValue().status().name());
     236             :             })
     237           9 :         .sorted()
     238           9 :         .collect(toImmutableList());
     239             :   }
     240             : }

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