blob: 40a4a3b77835c0b7f2c7dc8d88496a24fdde9ab1 [file] [log] [blame]
// 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);
}
}