Allow adding groups as reviewer

On the ChangeScreen it is now possible to add a group as reviewer for
a change. When a group is added as reviewer the group is resolved and
all its members are added as reviewers to the change.

To avoid that users accidentily add groups as reviewers that have a
large amount of members, Gerrit administrators can configure a
maximum number of reviewers that can be added at once by adding a
group as reviewer. In addition Gerrit administrators can configure
that users should confirm the adding of the reviewers if the number
of reviewers that should be added is over a certain limit.

It is also possible to add the system group 'Project Owners' as
reviewer. In this case all users which own the project are added as
reviewers.

If a user and a group have the same name, only the user is added as
reviewer.

Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
Bug: issue 881
Change-Id: I78a359f89ce045a81306e0a447384c37cda07d00
This commit is contained in:
Edwin Kempin
2011-06-29 14:35:14 +02:00
parent c4af33451e
commit 49cb3e126d
25 changed files with 701 additions and 108 deletions

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ReviewerResult;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
@@ -26,12 +27,17 @@ import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupMembersFactory;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.Config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -40,42 +46,56 @@ import java.util.Set;
import java.util.concurrent.Callable;
public class AddReviewer implements Callable<ReviewerResult> {
public final static int DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK = 10;
public final static int DEFAULT_MAX_REVIEWERS = 20;
public interface Factory {
AddReviewer create(Change.Id changeId, Collection<String> nameOrEmails);
AddReviewer create(Change.Id changeId,
Collection<String> userNameOrEmailOrGroupNames, boolean confirmed);
}
private final AddReviewerSender.Factory addReviewerSenderFactory;
private final AccountResolver accountResolver;
private final GroupCache groupCache;
private final GroupMembersFactory.Factory groupMembersFactory;
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ApprovalCategory.Id addReviewerCategoryId;
private final Config cfg;
private final Change.Id changeId;
private final Collection<String> reviewers;
private final boolean confirmed;
@Inject
AddReviewer(final AddReviewerSender.Factory addReviewerSenderFactory,
final AccountResolver accountResolver,
final AccountResolver accountResolver, final GroupCache groupCache,
final GroupMembersFactory.Factory groupMembersFactory,
final ChangeControl.Factory changeControlFactory, final ReviewDb db,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final IdentifiedUser currentUser, final ApprovalTypes approvalTypes,
@Assisted final Change.Id changeId,
@Assisted final Collection<String> nameOrEmails) {
final @GerritServerConfig Config cfg, @Assisted final Change.Id changeId,
@Assisted final Collection<String> reviewers,
@Assisted final boolean confirmed) {
this.addReviewerSenderFactory = addReviewerSenderFactory;
this.accountResolver = accountResolver;
this.groupCache = groupCache;
this.groupMembersFactory = groupMembersFactory;
this.db = db;
this.changeControlFactory = changeControlFactory;
this.identifiedUserFactory = identifiedUserFactory;
this.currentUser = currentUser;
this.cfg = cfg;
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
addReviewerCategoryId =
allTypes.get(allTypes.size() - 1).getCategory().getId();
this.changeId = changeId;
this.reviewers = nameOrEmails;
this.reviewers = reviewers;
this.confirmed = confirmed;
}
@Override
@@ -84,18 +104,73 @@ public class AddReviewer implements Callable<ReviewerResult> {
final ChangeControl control = changeControlFactory.validateFor(changeId);
final ReviewerResult result = new ReviewerResult();
for (final String nameOrEmail : reviewers) {
final Account account = accountResolver.find(nameOrEmail);
for (final String reviewer : reviewers) {
final Account account = accountResolver.find(reviewer);
if (account == null) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.ACCOUNT_NOT_FOUND, nameOrEmail));
AccountGroup group = groupCache.get(new AccountGroup.NameKey(reviewer));
if (group == null) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.REVIEWER_NOT_FOUND, reviewer));
continue;
}
if (!isLegalReviewerGroup(group.getGroupUUID())) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.GROUP_NOT_ALLOWED, reviewer));
continue;
}
final Set<Account> members =
groupMembersFactory.create(control.getProject().getNameKey(),
group.getGroupUUID()).call();
if (members == null || members.size() == 0) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.GROUP_EMPTY, reviewer));
continue;
}
// if maxAllowed is set to 0, it is allowed to add any number of
// reviewers
final int maxAllowed =
cfg.getInt("addreviewer", "maxAllowed", DEFAULT_MAX_REVIEWERS);
if (maxAllowed > 0 && members.size() > maxAllowed) {
result.setMemberCount(members.size());
result.setAskForConfirmation(false);
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.GROUP_HAS_TOO_MANY_MEMBERS, reviewer));
continue;
}
// if maxWithoutCheck is set to 0, we never ask for confirmation
final int maxWithoutConfirmation =
cfg.getInt("addreviewer", "maxWithoutConfirmation",
DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK);
if (!confirmed && maxWithoutConfirmation > 0
&& members.size() > maxWithoutConfirmation) {
result.setMemberCount(members.size());
result.setAskForConfirmation(true);
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.GROUP_HAS_TOO_MANY_MEMBERS, reviewer));
continue;
}
for (final Account member : members) {
if (member.isActive()) {
final IdentifiedUser user =
identifiedUserFactory.create(member.getId());
if (control.forUser(user).isVisible()) {
reviewerIds.add(member.getId());
}
}
}
continue;
}
if (!account.isActive()) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.ACCOUNT_INACTIVE,
formatUser(account, nameOrEmail)));
formatUser(account, reviewer)));
continue;
}
@@ -103,7 +178,7 @@ public class AddReviewer implements Callable<ReviewerResult> {
if (!control.forUser(user).isVisible()) {
result.addError(new ReviewerResult.Error(
ReviewerResult.Error.Type.CHANGE_NOT_VISIBLE,
formatUser(account, nameOrEmail)));
formatUser(account, reviewer)));
continue;
}
@@ -165,4 +240,9 @@ public class AddReviewer implements Callable<ReviewerResult> {
return new PatchSetApproval(new PatchSetApproval.Key(patchSetId,
reviewerId, addReviewerCategoryId), (short) 0);
}
public static boolean isLegalReviewerGroup(final AccountGroup.UUID groupUUID) {
return !(AccountGroup.ANONYMOUS_USERS.equals(groupUUID)
|| AccountGroup.REGISTERED_USERS.equals(groupUUID));
}
}