Line data Source code
1 : // Copyright (C) 2018 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.rules; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : 19 : import com.google.common.annotations.VisibleForTesting; 20 : import com.google.gerrit.entities.Account; 21 : import com.google.gerrit.entities.LabelFunction; 22 : import com.google.gerrit.entities.LabelType; 23 : import com.google.gerrit.entities.LegacySubmitRequirement; 24 : import com.google.gerrit.entities.PatchSetApproval; 25 : import com.google.gerrit.entities.SubmitRecord; 26 : import com.google.gerrit.extensions.annotations.Exports; 27 : import com.google.gerrit.server.query.change.ChangeData; 28 : import com.google.inject.AbstractModule; 29 : import com.google.inject.Singleton; 30 : import java.util.ArrayList; 31 : import java.util.Collection; 32 : import java.util.List; 33 : import java.util.Optional; 34 : 35 : /** 36 : * Rule to require an approval from a user that did not upload the current patch set or block 37 : * submission. 38 : */ 39 : @Singleton 40 146 : public class IgnoreSelfApprovalRule implements SubmitRule { 41 152 : public static class IgnoreSelfApprovalRuleModule extends AbstractModule { 42 : @Override 43 : public void configure() { 44 152 : bind(SubmitRule.class) 45 152 : .annotatedWith(Exports.named("IgnoreSelfApprovalRule")) 46 152 : .to(IgnoreSelfApprovalRule.class); 47 152 : } 48 : } 49 : 50 : @Override 51 : public Optional<SubmitRecord> evaluate(ChangeData cd) { 52 103 : List<LabelType> labelTypes = cd.getLabelTypes().getLabelTypes(); 53 103 : List<PatchSetApproval> approvals = cd.currentApprovals(); 54 103 : boolean shouldIgnoreSelfApproval = 55 103 : labelTypes.stream().anyMatch(LabelType::isIgnoreSelfApproval); 56 103 : if (!shouldIgnoreSelfApproval) { 57 : // Shortcut to avoid further processing if no label should ignore uploader approvals 58 103 : return Optional.empty(); 59 : } 60 : 61 2 : Account.Id uploader = cd.currentPatchSet().uploader(); 62 2 : SubmitRecord submitRecord = new SubmitRecord(); 63 2 : submitRecord.status = SubmitRecord.Status.OK; 64 2 : submitRecord.labels = new ArrayList<>(labelTypes.size()); 65 2 : submitRecord.requirements = new ArrayList<>(); 66 : 67 2 : for (LabelType t : labelTypes) { 68 2 : if (!t.isIgnoreSelfApproval()) { 69 : // The default rules are enough in this case. 70 1 : continue; 71 : } 72 : 73 2 : LabelFunction labelFunction = t.getFunction(); 74 2 : if (labelFunction == null) { 75 0 : continue; 76 : } 77 : 78 2 : Collection<PatchSetApproval> allApprovalsForLabel = filterApprovalsByLabel(approvals, t); 79 2 : SubmitRecord.Label allApprovalsCheckResult = labelFunction.check(t, allApprovalsForLabel); 80 2 : SubmitRecord.Label ignoreSelfApprovalCheckResult = 81 2 : labelFunction.check(t, filterOutPositiveApprovalsOfUser(allApprovalsForLabel, uploader)); 82 : 83 2 : if (labelCheckPassed(allApprovalsCheckResult) 84 2 : && !labelCheckPassed(ignoreSelfApprovalCheckResult)) { 85 : // The label has a valid approval from the uploader and no other valid approval. Set the 86 : // label 87 : // to NOT_READY and indicate the need for non-uploader approval as requirement. 88 2 : submitRecord.labels.add(ignoreSelfApprovalCheckResult); 89 2 : submitRecord.status = SubmitRecord.Status.NOT_READY; 90 : // Add an additional requirement to be more descriptive on why the label counts as not 91 : // approved. 92 2 : submitRecord.requirements.add( 93 2 : LegacySubmitRequirement.builder() 94 2 : .setFallbackText("Approval from non-uploader required") 95 2 : .setType("non_uploader_approval") 96 2 : .build()); 97 : } 98 2 : } 99 : 100 2 : if (submitRecord.labels.isEmpty()) { 101 2 : return Optional.empty(); 102 : } 103 : 104 2 : return Optional.of(submitRecord); 105 : } 106 : 107 : private static boolean labelCheckPassed(SubmitRecord.Label label) { 108 2 : switch (label.status) { 109 : case OK: 110 : case MAY: 111 2 : return true; 112 : 113 : case NEED: 114 : case REJECT: 115 : case IMPOSSIBLE: 116 2 : return false; 117 : } 118 0 : return false; 119 : } 120 : 121 : @VisibleForTesting 122 : static Collection<PatchSetApproval> filterOutPositiveApprovalsOfUser( 123 : Collection<PatchSetApproval> approvals, Account.Id user) { 124 3 : return approvals.stream() 125 3 : .filter(input -> input.value() < 0 || !input.accountId().equals(user)) 126 3 : .collect(toImmutableList()); 127 : } 128 : 129 : @VisibleForTesting 130 : static Collection<PatchSetApproval> filterApprovalsByLabel( 131 : Collection<PatchSetApproval> approvals, LabelType t) { 132 3 : return approvals.stream() 133 3 : .filter(input -> input.labelId().get().equals(t.getLabelId().get())) 134 3 : .collect(toImmutableList()); 135 : } 136 : }