diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java b/appjar/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java index 6c6d01c219..84a80cdb11 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/AccountGroupScreen.java @@ -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()); - ownerTxt.setText(result.ownerGroup.getName()); - descTxt.setText(result.group.getDescription()); + final AccountGroup group = result.group; + setTitleText(Util.M.group(group.getName())); + groupNameTxt.setText(group.getName()); + if (result.ownerGroup != null) { + ownerTxt.setText(result.ownerGroup.getName()); + } else { + ownerTxt.setText(Util.M.deletedGroup(group.getOwnerGroupId().get())); + } + descTxt.setText(group.getDescription()); if (result.autoGroup) { memberPanel.setVisible(false); } else { diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.java index 41df89fc79..0558c91c9e 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.java @@ -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(); diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index 625c506c21..9b003fd3c6 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/appjar/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties @@ -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 diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.java index 76b0480aed..e26ea516b8 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.java @@ -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); } diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties index 30414bb7c1..06123c71f3 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties +++ b/appjar/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties @@ -1,2 +1,4 @@ group = Group {0} project = Project {0} +deletedGroup = Deleted Group {0} + \ No newline at end of file diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java index 36f11c413d..c34f0a7f09 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminScreen.java @@ -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 { + 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 ids = new HashSet(); + 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() { + 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 groups, + final List 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 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("
"); + 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(""); + if (v == 0) { + m.append(' '); + } else if (v > 0) { + m.append('+'); + } + m.append(v); + m.append(""); + if (e != null) { + m.append(": "); + m.append(DomUtil.escape(e.getName())); + } + } } } diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java index 3eb5cee704..b65fb9b815 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectAdminService.java @@ -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 callback); + + @SignInRequired + void deleteRight(Set ids, AsyncCallback callback); } diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java index cbf95dcbde..752f1ec261 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectDetail.java @@ -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 groups; + protected List 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(); + wantGroup(g.getOwnerGroupId()); + + rights = new ArrayList(); + 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 r = db.accountGroups().get(groups.keySet()); + groups.clear(); + for (final AccountGroup g : r) { + groups.put(g.getId(), g); + } } } diff --git a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java index 2a21ebadba..57a97b4a70 100644 --- a/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java +++ b/appjar/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java @@ -71,8 +71,8 @@ public class ProjectListScreen extends AccountScreen { private class ProjectTable extends FancyFlexTable { 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) { diff --git a/appjar/src/main/java/com/google/gerrit/client/data/ApprovalType.java b/appjar/src/main/java/com/google/gerrit/client/data/ApprovalType.java index 00d8883f36..42f3f65e9a 100644 --- a/appjar/src/main/java/com/google/gerrit/client/data/ApprovalType.java +++ b/appjar/src/main/java/com/google/gerrit/client/data/ApprovalType.java @@ -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(); for (final ApprovalCategoryValue acv : values) { byValue.put(acv.getValue(), acv); } } - return byValue.get(ca.getValue()); } } diff --git a/appjar/src/main/java/com/google/gerrit/client/data/GerritConfig.java b/appjar/src/main/java/com/google/gerrit/client/data/GerritConfig.java index 417876775e..9ac4f11a94 100644 --- a/appjar/src/main/java/com/google/gerrit/client/data/GerritConfig.java +++ b/appjar/src/main/java/com/google/gerrit/client/data/GerritConfig.java @@ -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 approvalTypes; protected List actionTypes; protected int sshdPort; + private transient Map 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(); + 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); + } } diff --git a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectRight.java b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectRight.java index 9737999010..7c2f3e89a6 100644 --- a/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectRight.java +++ b/appjar/src/main/java/com/google/gerrit/client/reviewdb/ProjectRight.java @@ -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; } diff --git a/appjar/src/main/java/com/google/gerrit/pgm/ImportGerrit1.java b/appjar/src/main/java/com/google/gerrit/pgm/ImportGerrit1.java index 34c072b651..44b600976b 100644 --- a/appjar/src/main/java/com/google/gerrit/pgm/ImportGerrit1.java +++ b/appjar/src/main/java/com/google/gerrit/pgm/ImportGerrit1.java @@ -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)); } diff --git a/appjar/src/main/java/com/google/gerrit/public/Gerrit.css b/appjar/src/main/java/com/google/gerrit/public/Gerrit.css index c330add65d..d57490461e 100644 --- a/appjar/src/main/java/com/google/gerrit/public/Gerrit.css +++ b/appjar/src/main/java/com/google/gerrit/public/Gerrit.css @@ -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; +} diff --git a/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java index cee9248329..0b21143acd 100644 --- a/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java +++ b/appjar/src/main/java/com/google/gerrit/server/ProjectAdminServiceImpl.java @@ -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 keys, + final AsyncCallback callback) { + run(callback, new Action() { + public VoidResult run(final ReviewDb db) throws OrmException, Failure { + final Set 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 ids(final Collection projectList) { + final HashSet r = new HashSet(); + for (final Project project : projectList) { + r.add(project.getId()); + } + return r; + } }