Enable groups to manage contributor agreements

For a corporate style contributor agreement there are typically more
than one account which is covered by that agreement, and the CLA is
actually between the company and the project leaders, not the users
who are listed out on that agreement.  Permission as to who is an
authorized user under that agreement should be managed by the company
who made the agreement as employees enter the company, leave, or are
shifted to different job functions.

By creating a pair of groups in Gerrit, e.g. "Initech Users" and
"Initech Admins", with the later being the owner of the former,
the Initech personnel can manage their own authorized users list,
without involving the Gerrit site administrators.

Bug: GERRIT-17
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-06-08 12:26:58 -07:00
parent 828f3c71a5
commit e24a97229d
11 changed files with 297 additions and 49 deletions

View File

@@ -18,17 +18,22 @@ import com.google.gerrit.client.data.AccountInfoCache;
import com.google.gerrit.client.data.AccountInfoCacheFactory;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountAgreement;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.AccountGroupAgreement;
import com.google.gerrit.client.reviewdb.ContributorAgreement;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.Common;
import com.google.gwtorm.client.OrmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AgreementInfo {
protected AccountInfoCache accounts;
protected List<AccountAgreement> accepted;
protected List<AccountAgreement> userAccepted;
protected List<AccountGroupAgreement> groupAccepted;
protected Map<ContributorAgreement.Id, ContributorAgreement> agreements;
public AgreementInfo() {
@@ -37,9 +42,23 @@ public class AgreementInfo {
public void load(final Account.Id me, final ReviewDb db) throws OrmException {
final AccountInfoCacheFactory acc = new AccountInfoCacheFactory(db);
accepted = db.accountAgreements().byAccount(me).toList();
userAccepted = db.accountAgreements().byAccount(me).toList();
groupAccepted = new ArrayList<AccountGroupAgreement>();
for (final AccountGroup.Id groupId : Common.getGroupCache()
.getEffectiveGroups(me)) {
groupAccepted.addAll(db.accountGroupAgreements().byGroup(groupId)
.toList());
}
agreements = new HashMap<ContributorAgreement.Id, ContributorAgreement>();
for (final AccountAgreement a : accepted) {
for (final AccountAgreement a : userAccepted) {
acc.want(a.getReviewedBy());
if (!agreements.containsKey(a.getAgreementId())) {
agreements.put(a.getAgreementId(), db.contributorAgreements().get(
a.getAgreementId()));
}
}
for (final AccountGroupAgreement a : groupAccepted) {
acc.want(a.getReviewedBy());
if (!agreements.containsKey(a.getAgreementId())) {
agreements.put(a.getAgreementId(), db.contributorAgreements().get(

View File

@@ -16,7 +16,9 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Link;
import com.google.gerrit.client.reviewdb.AbstractAgreement;
import com.google.gerrit.client.reviewdb.AccountAgreement;
import com.google.gerrit.client.reviewdb.AccountGroupAgreement;
import com.google.gerrit.client.reviewdb.ContributorAgreement;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.FancyFlexTable;
@@ -51,7 +53,7 @@ class AgreementPanel extends Composite {
});
}
private class AgreementTable extends FancyFlexTable<AccountAgreement> {
private class AgreementTable extends FancyFlexTable<AbstractAgreement> {
AgreementTable() {
table.setText(0, 1, Util.C.agreementStatus());
table.setText(0, 2, Util.C.agreementName());
@@ -68,12 +70,15 @@ class AgreementPanel extends Composite {
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
for (final AccountAgreement k : result.accepted) {
for (final AccountAgreement k : result.userAccepted) {
addOne(result, k);
}
for (final AccountGroupAgreement k : result.groupAccepted) {
addOne(result, k);
}
}
void addOne(final AgreementInfo info, final AccountAgreement k) {
void addOne(final AgreementInfo info, final AbstractAgreement k) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);

View File

@@ -18,6 +18,7 @@ import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.Link;
import com.google.gerrit.client.reviewdb.AccountAgreement;
import com.google.gerrit.client.reviewdb.AccountGroupAgreement;
import com.google.gerrit.client.reviewdb.ContributorAgreement;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountScreen;
@@ -72,7 +73,10 @@ public class NewAgreementScreen extends AccountScreen {
public void onSuccess(AgreementInfo result) {
if (isAttached()) {
mySigned = new HashSet<ContributorAgreement.Id>();
for (AccountAgreement a : result.accepted) {
for (AccountAgreement a : result.userAccepted) {
mySigned.add(a.getAgreementId());
}
for (AccountGroupAgreement a : result.groupAccepted) {
mySigned.add(a.getAgreementId());
}
postRPC();

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2009 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.client.reviewdb;
import java.sql.Timestamp;
/** Base for {@link AccountAgreement} or {@link AccountGroupAgreement}. */
public interface AbstractAgreement {
public static enum Status {
NEW('n'),
VERIFIED('V'),
REJECTED('R');
private final char code;
private Status(final char c) {
code = c;
}
public char getCode() {
return code;
}
public static Status forCode(final char c) {
for (final Status s : Status.values()) {
if (s.code == c) {
return s;
}
}
return null;
}
}
public ContributorAgreement.Id getAgreementId();
public Timestamp getAcceptedOn();
public Status getStatus();
public Timestamp getReviewedOn();
public Account.Id getReviewedBy();
public String getReviewComments();
}

View File

@@ -20,7 +20,7 @@ import com.google.gwtorm.client.CompoundKey;
import java.sql.Timestamp;
/** Electronic acceptance of a {@link ContributorAgreement} by {@link Account} */
public final class AccountAgreement {
public final class AccountAgreement implements AbstractAgreement {
public static class Key extends CompoundKey<Account.Id> {
private static final long serialVersionUID = 1L;
@@ -51,36 +51,6 @@ public final class AccountAgreement {
}
}
protected static final char NEW_CODE = 'n';
public static enum Status {
NEW(NEW_CODE),
VERIFIED('V'),
REJECTED('R');
private final char code;
private Status(final char c) {
code = c;
}
public char getCode() {
return code;
}
public static Status forCode(final char c) {
for (final Status s : Status.values()) {
if (s.code == c) {
return s;
}
}
return null;
}
}
@Column(name = Column.NONE)
protected Key key;

View File

@@ -0,0 +1,114 @@
// Copyright (C) 2009 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.client.reviewdb;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
import java.sql.Timestamp;
/**
* Acceptance of a {@link ContributorAgreement} by an {@link AccountGroup}.
*/
public final class AccountGroupAgreement implements AbstractAgreement {
public static class Key extends CompoundKey<AccountGroup.Id> {
private static final long serialVersionUID = 1L;
@Column
protected AccountGroup.Id groupId;
@Column
protected ContributorAgreement.Id claId;
protected Key() {
groupId = new AccountGroup.Id();
claId = new ContributorAgreement.Id();
}
public Key(final AccountGroup.Id group, final ContributorAgreement.Id cla) {
this.groupId = group;
this.claId = cla;
}
@Override
public AccountGroup.Id getParentKey() {
return groupId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {claId};
}
}
@Column(name = Column.NONE)
protected Key key;
@Column
protected Timestamp acceptedOn;
@Column
protected char status;
@Column(notNull = false)
protected Account.Id reviewedBy;
@Column(notNull = false)
protected Timestamp reviewedOn;
@Column(notNull = false, length = Integer.MAX_VALUE)
protected String reviewComments;
protected AccountGroupAgreement() {
}
public AccountGroupAgreement(final AccountGroupAgreement.Key k) {
key = k;
acceptedOn = new Timestamp(System.currentTimeMillis());
status = Status.NEW.getCode();
}
public AccountGroupAgreement.Key getKey() {
return key;
}
public ContributorAgreement.Id getAgreementId() {
return key.claId;
}
public Timestamp getAcceptedOn() {
return acceptedOn;
}
public Status getStatus() {
return Status.forCode(status);
}
public Timestamp getReviewedOn() {
return reviewedOn;
}
public Account.Id getReviewedBy() {
return reviewedBy;
}
public String getReviewComments() {
return reviewComments;
}
public void setReviewComments(final String s) {
reviewComments = s;
}
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2009 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.client.reviewdb;
import com.google.gwtorm.client.Access;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.PrimaryKey;
import com.google.gwtorm.client.Query;
import com.google.gwtorm.client.ResultSet;
public interface AccountGroupAgreementAccess extends
Access<AccountGroupAgreement, AccountGroupAgreement.Key> {
@PrimaryKey("key")
AccountGroupAgreement get(AccountGroupAgreement.Key key) throws OrmException;
@Query("WHERE key.groupId = ? ORDER BY acceptedOn DESC")
ResultSet<AccountGroupAgreement> byGroup(AccountGroup.Id id)
throws OrmException;
}

View File

@@ -72,6 +72,9 @@ public interface ReviewDb extends Schema {
@Relation
AccountGroupMemberAuditAccess accountGroupMembersAudit();
@Relation
AccountGroupAgreementAccess accountGroupAgreements();
@Relation
StarredChangeAccess starredChanges();

View File

@@ -24,9 +24,12 @@ import static com.google.gerrit.client.reviewdb.ApprovalCategory.PUSH_TAG_ANY;
import com.google.gerrit.client.Link;
import com.google.gerrit.client.data.ApprovalType;
import com.google.gerrit.client.reviewdb.AbstractAgreement;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountAgreement;
import com.google.gerrit.client.reviewdb.AccountExternalId;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.AccountGroupAgreement;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.Branch;
import com.google.gerrit.client.reviewdb.Change;
@@ -199,20 +202,37 @@ class Receive extends AbstractGitCommand {
}
private void verifyActiveContributorAgreement() throws Failure {
AccountAgreement bestAgreement = null;
AbstractAgreement bestAgreement = null;
ContributorAgreement bestCla = null;
try {
for (final AccountAgreement a : db.accountAgreements().byAccount(
userAccount.getId()).toList()) {
final ContributorAgreement cla =
db.contributorAgreements().get(a.getAgreementId());
if (cla == null) {
continue;
}
OUTER: for (final AccountGroup.Id groupId : getGroups()) {
for (final AccountGroupAgreement a : db.accountGroupAgreements()
.byGroup(groupId)) {
final ContributorAgreement cla =
db.contributorAgreements().get(a.getAgreementId());
if (cla == null) {
continue;
}
bestAgreement = a;
bestCla = cla;
break;
bestAgreement = a;
bestCla = cla;
break OUTER;
}
}
if (bestAgreement == null) {
for (final AccountAgreement a : db.accountAgreements().byAccount(
userAccount.getId()).toList()) {
final ContributorAgreement cla =
db.contributorAgreements().get(a.getAgreementId());
if (cla == null) {
continue;
}
bestAgreement = a;
bestCla = cla;
break;
}
}
} catch (OrmException e) {
throw new Failure(1, "fatal: database error", e);

View File

@@ -21,4 +21,15 @@ WHERE group_id = (SELECT anonymous_group_id FROM system_config);
UPDATE account_groups SET automatic_membership = 'Y'
WHERE group_id = (SELECT registered_group_id FROM system_config);
CREATE TABLE account_group_agreements
(accepted_on TIMESTAMP NOT NULL
,status CHAR(1) NOT NULL
,reviewed_by INT
,reviewed_on TIMESTAMP
,review_comments TEXT
,group_id INT NOT NULL
,cla_id INT NOT NULL
,PRIMARY KEY (group_id, cla_id)
);
UPDATE schema_version SET version_nbr = 13;

View File

@@ -22,6 +22,18 @@ WHERE group_id = (SELECT anonymous_group_id FROM system_config);
UPDATE account_groups SET automatic_membership = 'Y'
WHERE group_id = (SELECT registered_group_id FROM system_config);
CREATE TABLE account_group_agreements
(accepted_on TIMESTAMP WITH TIME ZONE NOT NULL
,status CHAR(1) NOT NULL
,reviewed_by INT
,reviewed_on TIMESTAMP WITH TIME ZONE
,review_comments TEXT
,group_id INT NOT NULL
,cla_id INT NOT NULL
,PRIMARY KEY (group_id, cla_id)
);
ALTER TABLE account_group_members_audit OWNER TO gerrit2;
ALTER TABLE account_group_agreements OWNER TO gerrit2;
UPDATE schema_version SET version_nbr = 13;