| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.plugins.codeowners.backend; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.gerrit.common.Nullable; |
| import java.util.Optional; |
| |
| /** |
| * Scores by which we rate how good we consider a code owner as reviewer/approver for a certain |
| * path. |
| */ |
| public enum CodeOwnerScore { |
| /** |
| * The distance of the code owner configuration that defines the code owner from the owned path. |
| * |
| * <p>The smaller the distance the better we consider the code owner as reviewer/approver for the |
| * path. |
| * |
| * <p>Example: If there are 2 owner configurations '/A' and '/foo/bar/B' and we are computing the |
| * code owners for '/foo/bar/baz/, it can be that user X is a code owner of '/foo/bar/baz/' due to |
| * the owner configuration '/A' and user Y is a code owner of '/foo/bar/baz/' due to the owner |
| * configuration '/foo/bar/B'. Then user X has a distance of 3 (since the folder in which the |
| * owner configuration 'A' is stored, '/', has 3 path segments less than the path '/foo/bar/baz/' |
| * for which we are looking up code owners) and user Y has a distance of 1 (since the folder in |
| * which the owner configuration 'B' is stored, '/foo/bar', has 1 path segment less than the path |
| * '/foo/bar/baz/' for which we are looking up code owners). This means, looking at the distance, |
| * user Y is a better reviewer/approver for '/foo/bar/baz/' than user X as they have a lower |
| * distance. |
| */ |
| DISTANCE(Kind.LOWER_VALUE_IS_BETTER, /* weight= */ 1, /* maxValue= */ null), |
| |
| /** |
| * Score to take into account whether a code owner is a reviewer. |
| * |
| * <p>Code owners that are reviewers get scored with 1 (see {@link #IS_REVIEWER_SCORING_VALUE}), |
| * while code owners that are not a reviewer get scored with 0 (see {@link |
| * #NO_REVIEWER_SCORING_VALUE}). |
| * |
| * <p>The IS_REVIEWER score has a higher weight than the {@link #DISTANCE} score so that it takes |
| * precedence and code owners that are reviewers are always returned first. |
| */ |
| IS_REVIEWER(Kind.GREATER_VALUE_IS_BETTER, /* weight= */ 2, /* maxValue= */ 1), |
| |
| /** |
| * Score to take into account when a user is explicitly mentioned as a code owner |
| * |
| * <p>Users that are explicitly mentioned as code owner in a code owner config file get scored |
| * with 1 (see {@link #IS_EXPLICITLY_MENTIONED_SCORING_VALUE}), while users that are not |
| * explicitly mentioned as code owners in the code owner config file, and are only code owners |
| * because the code ownership is assigned to all users aka {@code *}, get scored with 0 (see |
| * {@link #NOT_EXPLICITLY_MENTIONED_SCORING_VALUE}). |
| * |
| * <p>The IS_EXPLICITLY_MENTIONED score has a lower weight than the {@link #DISTANCE} score so |
| * that the {@link #DISTANCE} score takes precedence. |
| */ |
| IS_EXPLICITLY_MENTIONED(Kind.GREATER_VALUE_IS_BETTER, /* weight= */ 0.5, /* maxValue= */ 1); |
| |
| /** |
| * Scoring value for the {@link #IS_REVIEWER} score for users that are not a reviewer of the |
| * change. |
| */ |
| public static int NO_REVIEWER_SCORING_VALUE = 0; |
| |
| /** |
| * Scoring value for the {@link #IS_REVIEWER} score for users that are a reviewer of the change. |
| */ |
| public static int IS_REVIEWER_SCORING_VALUE = 1; |
| |
| /** |
| * Scoring value for the {@link #IS_EXPLICITLY_MENTIONED} score for users that are not explicitly |
| * mentioned as code owners in the code owner config file and are only code owners because the |
| * code ownership is assigned to all users aka {@code *}. |
| */ |
| public static int NOT_EXPLICITLY_MENTIONED_SCORING_VALUE = 0; |
| |
| /** |
| * Scoring value for the {@link #IS_EXPLICITLY_MENTIONED} score for users that are explicitly |
| * mentioned as code owners in the code owner config file. |
| */ |
| public static int IS_EXPLICITLY_MENTIONED_SCORING_VALUE = 1; |
| |
| /** |
| * Score kind. |
| * |
| * <p>Whether a greater value as scoring is better than a lower value ({@code |
| * GREATER_VALUE_IS_BETTER}), or vice-versa ({@code LOWER_VALUE_IS_BETTER}). |
| */ |
| private enum Kind { |
| GREATER_VALUE_IS_BETTER, |
| LOWER_VALUE_IS_BETTER; |
| } |
| |
| /** |
| * Score kind. |
| * |
| * <p>Whether a greater value as scoring is better than a lower value ({@link |
| * Kind#GREATER_VALUE_IS_BETTER}), or vice-versa ({@link Kind#LOWER_VALUE_IS_BETTER}). |
| */ |
| private final Kind kind; |
| |
| /** |
| * The weight that this score should have when sorting code owners. |
| * |
| * <p>The higher the weight the larger the impact that this score has on the sorting. |
| */ |
| private final double weight; |
| |
| /** |
| * The max value that this score can have. |
| * |
| * <p>Not set if max value is not hard-coded, but is different case by case. |
| * |
| * <p>For scores that have a max value set scorings must be created by the {@link |
| * #createScoring()} method, for scores with flexible max values (maxValue = null) scorings must |
| * be created by the {@link #createScoring(int)} method. |
| */ |
| @Nullable private final Integer maxValue; |
| |
| private CodeOwnerScore(Kind kind, double weight, @Nullable Integer maxValue) { |
| this.kind = kind; |
| this.weight = weight; |
| this.maxValue = maxValue; |
| } |
| |
| /** |
| * Creates a {@link CodeOwnerScoring.Builder} instance for this score. |
| * |
| * <p>Use {@link #createScoring()} instead if the score has a max value set. |
| * |
| * @param maxValue the max possible scoring value |
| * @return the created {@link CodeOwnerScoring.Builder} instance |
| */ |
| public CodeOwnerScoring.Builder createScoring(int maxValue) { |
| checkState( |
| this.maxValue == null, |
| "score %s has defined a maxValue, setting maxValue not allowed", |
| name()); |
| return CodeOwnerScoring.builder(this, maxValue); |
| } |
| |
| /** |
| * Creates a {@link CodeOwnerScoring.Builder} instance for this score. |
| * |
| * <p>Use {@link #createScoring(int)} instead if the score doesn't have a max value set. |
| * |
| * @return the created {@link CodeOwnerScoring.Builder} instance |
| */ |
| public CodeOwnerScoring.Builder createScoring() { |
| checkState( |
| maxValue != null, |
| "score %s doesn't have a maxValue defined, setting maxValue is required", |
| name()); |
| return CodeOwnerScoring.builder(this, maxValue); |
| } |
| |
| /** |
| * Whether for this score a lower value is considered better than a greater value. |
| * |
| * @return whether for this score a lower value is considered better than a greater value. |
| */ |
| boolean isLowerValueBetter() { |
| return Kind.LOWER_VALUE_IS_BETTER.equals(kind); |
| } |
| |
| double weight() { |
| return weight; |
| } |
| |
| Optional<Integer> maxValue() { |
| return Optional.ofNullable(maxValue); |
| } |
| } |