Show the approval rights for a project in the project admin screen

This way its clear what rights have been granted out on this project
to any existing groups.

If the right isn't given to the magic wildcard project then the right
can be deleted from the project.  Wildcard rights must be managed by
the site administrators only, and may initially only be available from
within the raw database.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-01-03 14:57:28 -08:00
parent a26dc96cf8
commit d0207f556a
15 changed files with 376 additions and 16 deletions

View File

@@ -252,10 +252,15 @@ public class AccountGroupScreen extends AccountScreen {
}
private void display(final AccountGroupDetail result) {
setTitleText(Util.M.group(result.group.getName()));
groupNameTxt.setText(result.group.getName());
final AccountGroup group = result.group;
setTitleText(Util.M.group(group.getName()));
groupNameTxt.setText(group.getName());
if (result.ownerGroup != null) {
ownerTxt.setText(result.ownerGroup.getName());
descTxt.setText(result.group.getDescription());
} else {
ownerTxt.setText(Util.M.deletedGroup(group.getOwnerGroupId().get()));
}
descTxt.setText(group.getDescription());
if (result.autoGroup) {
memberPanel.setVisible(false);
} else {

View File

@@ -30,11 +30,16 @@ public interface AdminConstants extends Constants {
String headingDescription();
String headingMembers();
String headingCreateGroup();
String headingAccessRights();
String columnMember();
String columnEmailAddress();
String columnGroupName();
String columnProjectName();
String columnGroupDescription();
String columnProjectDescription();
String columnApprovalCategory();
String columnRightRange();
String groupListTitle();
String projectListTitle();

View File

@@ -11,11 +11,16 @@ headingOwner = Owners
headingDescription = Description
headingMembers = Members
headingCreateGroup = Create New Group
headingAccessRights = Access Rights
columnMember = Member
columnEmailAddress = Email Address
columnGroupName = Name
columnGroupName = Group Name
columnProjectName = Project Name
columnGroupDescription = Description
columnProjectDescription = Description
columnApprovalCategory = Category
columnRightRange = Permitted Range
groupListTitle = Groups
projectListTitle = Projects

View File

@@ -19,4 +19,5 @@ import com.google.gwt.i18n.client.Messages;
public interface AdminMessages extends Messages {
String group(String name);
String project(String name);
String deletedGroup(int id);
}

View File

@@ -1,2 +1,4 @@
group = Group {0}
project = Project {0}
deletedGroup = Deleted Group {0}

View File

@@ -14,21 +14,37 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.data.ApprovalType;
import com.google.gerrit.client.data.GerritConfig;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.client.ui.DomUtil;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwtjsonrpc.client.VoidResult;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
public class ProjectAdminScreen extends AccountScreen {
private Project.Id projectId;
@@ -39,6 +55,9 @@ public class ProjectAdminScreen extends AccountScreen {
private TextArea descTxt;
private Button saveDesc;
private RightsTable rights;
private Button delRight;
public ProjectAdminScreen(final Project.Id toShow) {
projectId = toShow;
}
@@ -68,14 +87,15 @@ public class ProjectAdminScreen extends AccountScreen {
private void enableForm(final boolean on) {
ownerTxtBox.setEnabled(on);
descTxt.setEnabled(on);
delRight.setEnabled(on);
}
private void initUI() {
initOwner();
initDescription();
initRights();
}
private void initOwner() {
final VerticalPanel ownerPanel = new VerticalPanel();
final Label ownerHdr = new Label(Util.C.headingOwner());
@@ -136,9 +156,187 @@ public class ProjectAdminScreen extends AccountScreen {
new TextSaveButtonListener(descTxt, saveDesc);
}
private void initRights() {
final Label rightsHdr = new Label(Util.C.headingAccessRights());
rightsHdr.setStyleName("gerrit-SmallHeading");
rights = new RightsTable();
delRight = new Button(Util.C.buttonDeleteGroupMembers());
delRight.addClickListener(new ClickListener() {
public void onClick(final Widget sender) {
rights.deleteChecked();
}
});
add(rightsHdr);
add(rights);
add(delRight);
}
private void display(final ProjectDetail result) {
setTitleText(Util.M.project(result.project.getName()));
ownerTxt.setText(result.ownerGroup.getName());
descTxt.setText(result.project.getDescription());
final Project project = result.project;
final AccountGroup owner = result.groups.get(project.getOwnerGroupId());
setTitleText(Util.M.project(project.getName()));
if (owner != null) {
ownerTxt.setText(owner.getName());
} else {
ownerTxt.setText(Util.M.deletedGroup(project.getOwnerGroupId().get()));
}
descTxt.setText(project.getDescription());
rights.display(result.groups, result.rights);
}
private class RightsTable extends FancyFlexTable<ProjectRight> {
RightsTable() {
table.setText(0, 2, Util.C.columnApprovalCategory());
table.setText(0, 3, Util.C.columnGroupName());
table.setText(0, 4, Util.C.columnRightRange());
table.addTableListener(new TableListener() {
public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
if (cell != 1 && getRowItem(row) != null) {
movePointerTo(row);
}
}
});
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(0, 1, S_ICON_HEADER);
fmt.addStyleName(0, 2, S_DATA_HEADER);
fmt.addStyleName(0, 3, S_DATA_HEADER);
fmt.addStyleName(0, 4, S_DATA_HEADER);
}
@Override
protected Object getRowItemKey(final ProjectRight item) {
return item.getKey();
}
@Override
protected boolean onKeyPress(final char keyCode, final int modifiers) {
if (super.onKeyPress(keyCode, modifiers)) {
return true;
}
if (modifiers == 0) {
switch (keyCode) {
case 's':
case 'c':
toggleCurrentRow();
return true;
}
}
return false;
}
@Override
protected void onOpenItem(final ProjectRight item) {
toggleCurrentRow();
}
private void toggleCurrentRow() {
final CheckBox cb = (CheckBox) table.getWidget(getCurrentRow(), 1);
cb.setChecked(!cb.isChecked());
}
void deleteChecked() {
final HashSet<ProjectRight.Key> ids = new HashSet<ProjectRight.Key>();
for (int row = 1; row < table.getRowCount(); row++) {
final ProjectRight k = getRowItem(row);
if (k != null && table.getWidget(row, 1) instanceof CheckBox
&& ((CheckBox) table.getWidget(row, 1)).isChecked()) {
ids.add(k.getKey());
}
}
if (!ids.isEmpty()) {
Util.PROJECT_SVC.deleteRight(ids, new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final ProjectRight k = getRowItem(row);
if (k != null && ids.contains(k.getKey())) {
table.removeRow(row);
} else {
row++;
}
}
}
});
}
}
void display(final Map<AccountGroup.Id, AccountGroup> groups,
final List<ProjectRight> result) {
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
for (final ProjectRight k : result) {
final int row = table.getRowCount();
table.insertRow(row);
populate(row, groups, k);
}
}
void populate(final int row,
final Map<AccountGroup.Id, AccountGroup> groups, final ProjectRight k) {
final GerritConfig config = Gerrit.getGerritConfig();
final ApprovalType ar = config.getApprovalType(k.getApprovalCategoryId());
final AccountGroup group = groups.get(k.getAccountGroupId());
if (ProjectRight.WILD_PROJECT.equals(k.getProjectId())) {
table.setText(row, 1, "");
} else {
table.setWidget(row, 1, new CheckBox());
}
if (ar != null) {
table.setText(row, 2, ar.getCategory().getName());
} else {
table.setText(row, 2, k.getApprovalCategoryId().get());
}
if (group != null) {
table.setText(row, 3, group.getName());
} else {
table.setText(row, 3, Util.M.deletedGroup(k.getAccountGroupId().get()));
}
{
final StringBuilder m = new StringBuilder();
final ApprovalCategoryValue min, max;
min = ar != null ? ar.getValue(k.getMinValue()) : null;
max = ar != null ? ar.getValue(k.getMaxValue()) : null;
formatValue(m, k.getMinValue(), min);
if (k.getMinValue() != k.getMaxValue()) {
m.append("<br>");
formatValue(m, k.getMaxValue(), max);
}
table.setHTML(row, 4, m.toString());
}
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(row, 1, S_ICON_CELL);
fmt.addStyleName(row, 2, S_DATA_CELL);
fmt.addStyleName(row, 3, S_DATA_CELL);
fmt.addStyleName(row, 4, S_DATA_CELL);
fmt.addStyleName(row, 4, "gerrit-ProjectAdmin-ApprovalCategoryRangeLine");
setRowItem(row, k);
}
private void formatValue(final StringBuilder m, final short v,
final ApprovalCategoryValue e) {
m.append("<span class=\"gerrit-ProjectAdmin-ApprovalCategoryValue\">");
if (v == 0) {
m.append(' ');
} else if (v > 0) {
m.append('+');
}
m.append(v);
m.append("</span>");
if (e != null) {
m.append(": ");
m.append(DomUtil.escape(e.getName()));
}
}
}
}

View File

@@ -15,12 +15,14 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.rpc.SignInRequired;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.client.VoidResult;
import java.util.List;
import java.util.Set;
public interface ProjectAdminService extends RemoteJsonService {
@SignInRequired
@@ -36,4 +38,7 @@ public interface ProjectAdminService extends RemoteJsonService {
@SignInRequired
void changeProjectOwner(Project.Id projectId, String newOwnerName,
AsyncCallback<VoidResult> callback);
@SignInRequired
void deleteRight(Set<ProjectRight.Key> ids, AsyncCallback<VoidResult> callback);
}

View File

@@ -16,18 +16,53 @@ package com.google.gerrit.client.admin;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ProjectDetail {
protected Project project;
protected AccountGroup ownerGroup;
protected Map<AccountGroup.Id, AccountGroup> groups;
protected List<ProjectRight> rights;
public ProjectDetail() {
}
public void load(final ReviewDb db, final Project g) throws OrmException {
project = g;
ownerGroup = db.accountGroups().get(project.getOwnerGroupId());
groups = new HashMap<AccountGroup.Id, AccountGroup>();
wantGroup(g.getOwnerGroupId());
rights = new ArrayList<ProjectRight>();
loadRights(db, project.getId());
loadRights(db, ProjectRight.WILD_PROJECT);
loadGroups(db);
}
private void loadRights(final ReviewDb db, final Project.Id projectId)
throws OrmException {
for (final ProjectRight p : db.projectRights().byProject(projectId)) {
rights.add(p);
wantGroup(p.getAccountGroupId());
}
}
private void wantGroup(final AccountGroup.Id id) {
groups.put(id, null);
}
private void loadGroups(final ReviewDb db) throws OrmException {
final ResultSet<AccountGroup> r = db.accountGroups().get(groups.keySet());
groups.clear();
for (final AccountGroup g : r) {
groups.put(g.getId(), g);
}
}
}

View File

@@ -71,8 +71,8 @@ public class ProjectListScreen extends AccountScreen {
private class ProjectTable extends FancyFlexTable<Project> {
ProjectTable() {
table.setText(0, 1, Util.C.columnGroupName());
table.setText(0, 2, Util.C.columnGroupDescription());
table.setText(0, 1, Util.C.columnProjectName());
table.setText(0, 2, Util.C.columnProjectDescription());
table.addTableListener(new TableListener() {
public void onCellClicked(SourcesTableEvents sender, int row, int cell) {
if (cell != 1 && getRowItem(row) != null) {

View File

@@ -74,13 +74,22 @@ public class ApprovalType {
return maxPositive == ca.getValue();
}
public ApprovalCategoryValue getValue(final short value) {
initByValue();
return byValue.get(value);
}
public ApprovalCategoryValue getValue(final ChangeApproval ca) {
initByValue();
return byValue.get(ca.getValue());
}
private void initByValue() {
if (byValue == null) {
byValue = new HashMap<Short, ApprovalCategoryValue>();
for (final ApprovalCategoryValue acv : values) {
byValue.put(acv.getValue(), acv);
}
}
return byValue.get(ca.getValue());
}
}

View File

@@ -14,8 +14,12 @@
package com.google.gerrit.client.data;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GerritConfig {
protected String canonicalUrl;
@@ -23,6 +27,7 @@ public class GerritConfig {
protected List<ApprovalType> approvalTypes;
protected List<ApprovalType> actionTypes;
protected int sshdPort;
private transient Map<ApprovalCategory.Id, ApprovalType> byCategoryId;
public GerritConfig() {
}
@@ -82,4 +87,22 @@ public class GerritConfig {
public void setSshdPort(final int p) {
sshdPort = p;
}
public ApprovalType getApprovalType(final ApprovalCategory.Id id) {
if (byCategoryId == null) {
byCategoryId = new HashMap<ApprovalCategory.Id, ApprovalType>();
if (actionTypes != null) {
for (final ApprovalType t : actionTypes) {
byCategoryId.put(t.getCategory().getId(), t);
}
}
if (approvalTypes != null) {
for (final ApprovalType t : approvalTypes) {
byCategoryId.put(t.getCategory().getId(), t);
}
}
}
return byCategoryId.get(id);
}
}

View File

@@ -50,6 +50,10 @@ public final class ProjectRight {
return projectId;
}
public Project.Id getProjectId() {
return projectId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {categoryId, groupId};
@@ -76,6 +80,18 @@ public final class ProjectRight {
return key;
}
public Project.Id getProjectId() {
return key.projectId;
}
public ApprovalCategory.Id getApprovalCategoryId() {
return key.categoryId;
}
public AccountGroup.Id getAccountGroupId() {
return key.groupId;
}
public short getMinValue() {
return minValue;
}

View File

@@ -203,8 +203,18 @@ public class ImportGerrit1 {
final ProjectRight.Key key =
new ProjectRight.Key(proj.getId(), category.getId(), groupId);
final ProjectRight pr = new ProjectRight(key);
pr.setMinValue(Short.MIN_VALUE);
pr.setMaxValue(Short.MAX_VALUE);
if (category == approveCategory) {
pr.setMinValue((short) -2);
pr.setMaxValue((short) 2);
} else if (category == verifyCategory) {
pr.setMinValue((short) -1);
pr.setMaxValue((short) 1);
} else if (category == submitCategory) {
pr.setMinValue((short) 1);
pr.setMaxValue((short) 1);
} else {
throw new OrmException("Cannot import category " + category.getId());
}
db.projectRights().insert(Collections.singleton(pr));
}

View File

@@ -543,3 +543,10 @@
padding: 5px 5px 5px 5px;
border: 1px solid #b0bdcc;
}
.gerrit-ProjectAdmin-ApprovalCategoryRangeLine {
white-space: nowrap;
}
.gerrit-ProjectAdmin-ApprovalCategoryValue {
font-family: Courier New, Courier, monospace;
}

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.client.admin.ProjectDetail;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.BaseServiceImplementation;
import com.google.gerrit.client.rpc.NoSuchEntityException;
@@ -28,9 +29,12 @@ import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ProjectAdminServiceImpl extends BaseServiceImplementation
implements ProjectAdminService {
@@ -116,6 +120,33 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
});
}
public void deleteRight(final Set<ProjectRight.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Set<Project.Id> owned = ids(myOwnedProjects(db));
Boolean amAdmin = null;
for (final ProjectRight.Key k : keys) {
if (!owned.contains(k.getProjectId())) {
if (amAdmin == null) {
amAdmin = groupCache.isAdministrator(RpcUtil.getAccountId());
}
if (!amAdmin) {
throw new Failure(new NoSuchEntityException());
}
}
}
for (final ProjectRight.Key k : keys) {
final ProjectRight m = db.projectRights().get(k);
if (m != null) {
db.projectRights().delete(Collections.singleton(m));
}
}
return VoidResult.INSTANCE;
}
});
}
private void assertAmProjectOwner(final ReviewDb db,
final Project.Id projectId) throws OrmException, Failure {
final Project project = db.projects().get(projectId);
@@ -139,4 +170,12 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
}
return own;
}
private static Set<Project.Id> ids(final Collection<Project> projectList) {
final HashSet<Project.Id> r = new HashSet<Project.Id>();
for (final Project project : projectList) {
r.add(project.getId());
}
return r;
}
}