Allow group descriptions to supply email and URL

Some backends have external management interfaces that are not
embedded into Gerrit Code Review. Allow those backends to supply
a URL to the web management interface for a group, so a user can
manage their membership, view current members, or do whatever other
features the group system might support.

Some backends also have an email address associated with every
group. Sending email to that address will distribute the message to
the group's members. Permit backends to supply an optional email
address, and use this in the project level notification system if
a group is selected as the target for a message.

Change-Id: Ifaebc01571c2db84872b2c08ff99c05389372f61
This commit is contained in:
Shawn Pearce
2013-01-18 14:29:59 -08:00
parent c65c508a88
commit f2145b401f
13 changed files with 121 additions and 39 deletions

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.common;
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -101,6 +102,10 @@ public class PageLinks {
return status(status) + " " + op("project", proj.get());
}
public static String toGroup(AccountGroup.UUID uuid) {
return ADMIN_GROUPS + "uuid-" + uuid;
}
private static String status(Status status) {
switch (status) {
case ABANDONED:

View File

@@ -16,6 +16,8 @@ package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.client.AccountGroup;
import javax.annotation.Nullable;
/**
* Group methods exposed by the GroupBackend.
*/
@@ -32,6 +34,22 @@ public class GroupDescription {
/** @return whether the group is visible to all accounts. */
boolean isVisibleToAll();
/**
* @return optional email address to send to the group's members. If
* provided, Gerrit will use this email address to send
* change notifications to the group.
*/
@Nullable
String getEmailAddress();
/**
* @return optional URL to information about the group. Typically a URL to a
* web page that permits users to apply to join the group, or manage
* their membership.
*/
@Nullable
String getUrl();
}
/**

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.common.data;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.AccountGroup;
import javax.annotation.Nullable;
@@ -52,6 +53,18 @@ public class GroupDescriptions {
public AccountGroup getAccountGroup() {
return group;
}
@Override
@Nullable
public String getEmailAddress() {
return null;
}
@Override
@Nullable
public String getUrl() {
return "#" + PageLinks.toGroup(getGroupUUID());
}
};
}

View File

@@ -21,6 +21,7 @@ public class GroupInfo {
protected AccountGroup.UUID uuid;
protected String name;
protected String description;
protected String url;
protected GroupInfo() {
}
@@ -44,6 +45,7 @@ public class GroupInfo {
public GroupInfo(GroupDescription.Basic a) {
uuid = a.getGroupUUID();
name = a.getName();
url = a.getUrl();
if (a instanceof GroupDescription.Internal) {
AccountGroup group = ((GroupDescription.Internal) a).getAccountGroup();
@@ -65,4 +67,8 @@ public class GroupInfo {
public String getDescription() {
return description;
}
public String getUrl() {
return url;
}
}

View File

@@ -145,12 +145,12 @@ public class Dispatcher {
return "/admin/groups/" + id.toString() + "," + panel;
}
public static String toGroup(final AccountGroup.UUID uuid) {
return "/admin/groups/uuid-" + uuid.toString();
public static String toGroup(AccountGroup.UUID uuid) {
return PageLinks.toGroup(uuid);
}
public static String toGroup(AccountGroup.UUID uuid, String panel) {
return "/admin/groups/uuid-" + uuid.toString() + "," + panel;
return toGroup(uuid) + "," + panel;
}
public static String toProject(Project.NameKey n) {

View File

@@ -24,7 +24,7 @@ public class MyGroupsScreen extends SettingsScreen {
@Override
protected void onInitUI() {
super.onInitUI();
groups = new GroupTable(true /* hyperlink to admin */);
groups = new GroupTable();
add(groups);
}

View File

@@ -35,6 +35,7 @@ import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
@@ -369,6 +370,14 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
new Hyperlink(info.getName(), Dispatcher.toGroup(uuid)));
fmt.getElement(row, 2).setTitle(null);
table.setText(row, 3, info.getDescription());
} else if (info.getUrl() != null) {
Anchor a = new Anchor();
a.setText(info.getName());
a.setHref(info.getUrl());
a.setTitle("UUID " + uuid.get());
table.setWidget(row, 2, a);
fmt.getElement(row, 2).setTitle(null);
table.clearCell(row, 3);
} else {
table.setText(row, 2, info.getName());
fmt.getElement(row, 2).setTitle("UUID " + uuid.get());

View File

@@ -82,7 +82,7 @@ public class GroupListScreen extends AccountScreen implements FilteredUserInterf
setPageTitle(Util.C.groupListTitle());
initPageHeader();
groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS);
groups = new GroupTable(PageLinks.ADMIN_GROUPS);
add(groups);
}

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.client.admin;
import static com.google.gerrit.client.admin.Util.C;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.groups.GroupInfo;
@@ -21,9 +22,12 @@ import com.google.gerrit.client.groups.GroupList;
import com.google.gerrit.client.groups.GroupMap;
import com.google.gerrit.client.ui.HighlightingInlineHyperlink;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.Util;
import com.google.gerrit.common.PageLinks;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.Image;
@@ -36,20 +40,17 @@ import java.util.List;
public class GroupTable extends NavigationTable<GroupInfo> {
private static final int NUM_COLS = 3;
private final boolean enableLink;
public GroupTable(final boolean enableLink) {
this(enableLink, null);
public GroupTable() {
this(null);
}
public GroupTable(final boolean enableLink, final String pointerId) {
super(Util.C.groupItemHelp());
this.enableLink = enableLink;
public GroupTable(final String pointerId) {
super(C.groupItemHelp());
setSavePointerId(pointerId);
table.setText(0, 1, Util.C.columnGroupName());
table.setText(0, 2, Util.C.columnGroupDescription());
table.setText(0, 3, Util.C.columnGroupVisibleToAll());
table.setText(0, 1, C.columnGroupName());
table.setText(0, 2, C.columnGroupDescription());
table.setText(0, 3, C.columnGroupVisibleToAll());
table.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
@@ -104,11 +105,18 @@ public class GroupTable extends NavigationTable<GroupInfo> {
}
void populate(final int row, final GroupInfo k, final String toHighlight) {
if (enableLink) {
table.setWidget(row, 1, new HighlightingInlineHyperlink(k.name(),
Dispatcher.toGroup(k.getGroupId()), toHighlight));
if (k.url() != null) {
if (k.url().startsWith("#" + PageLinks.ADMIN_GROUPS)) {
table.setWidget(row, 1, new HighlightingInlineHyperlink(k.name(),
Dispatcher.toGroup(k.getGroupId()), toHighlight));
} else {
Anchor link = new Anchor();
link.setHTML(Util.highlight(k.name(), toHighlight));
link.setHref(k.url());
table.setWidget(row, 1, link);
}
} else {
table.setText(row, 1, k.name());
table.setHTML(row, 1, Util.highlight(k.name(), toHighlight));
}
table.setText(row, 2, k.description());
if (k.isVisibleToAll()) {

View File

@@ -31,6 +31,7 @@ public class GroupInfo extends JavaScriptObject {
public final native String name() /*-{ return this.name; }-*/;
public final native boolean isVisibleToAll() /*-{ return this['visible_to_all'] ? true : false; }-*/;
public final native String description() /*-{ return this.description; }-*/;
public final native String url() /*-{ return this.url; }-*/;
private final native int group_id() /*-{ return this.group_id; }-*/;
private final native String owner_uuid() /*-{ return this.owner_uuid; }-*/;

View File

@@ -47,6 +47,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
@@ -152,6 +153,18 @@ public class LdapGroupBackend implements GroupBackend {
public boolean isVisibleToAll() {
return false;
}
@Override
@Nullable
public String getEmailAddress() {
return null;
}
@Override
@Nullable
public String getUrl() {
return null;
}
};
}

View File

@@ -21,8 +21,9 @@ import com.google.gerrit.server.util.Url;
public class GroupInfo {
final String kind = "gerritcodereview#group";
public String id;
public String name;
String id;
String name;
String url;
Boolean visibleToAll;
// These fields are only supplied for internal groups.
@@ -33,6 +34,7 @@ public class GroupInfo {
public GroupInfo(GroupDescription.Basic group) {
id = Url.encode(group.getGroupUUID().get());
name = Strings.emptyToNull(group.getName());
url = Strings.emptyToNull(group.getUrl());
visibleToAll = group.isVisibleToAll() ? true : null;
if (group instanceof GroupDescription.Internal) {

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.server.mail;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.EmailException;
@@ -418,22 +420,7 @@ public abstract class ChangeEmail extends NotificationEmail {
private void add(Watchers matching, NotifyConfig nc, Project.NameKey project)
throws OrmException, QueryParseException {
for (GroupReference ref : nc.getGroups()) {
AccountGroup group =
GroupDescriptions.toAccountGroup(args.groupBackend.get(ref.getUUID()));
if (group == null) {
log.warn(String.format(
"Project %s has invalid group %s in notify section %s",
project.get(), ref.getName(), nc.getName()));
continue;
}
if (group.getType() != AccountGroup.Type.INTERNAL) {
log.warn(String.format(
"Project %s cannot use group %s of type %s in notify section %s",
project.get(), ref.getName(), group.getType(), nc.getName()));
continue;
}
GroupDescription.Basic group = args.groupBackend.get(ref.getUUID());
ChangeQueryBuilder qb = args.queryBuilder.create(new SingleGroupUser(
args.capabilityControlFactory,
ref.getUUID()));
@@ -443,9 +430,29 @@ public abstract class ChangeEmail extends NotificationEmail {
p = Predicate.and(qb.parse(nc.getFilter()), p);
p = args.queryRewriter.get().rewrite(p);
}
if (p.match(changeData)) {
recursivelyAddAllAccounts(matching.list(nc.getHeader()), group);
if (!p.match(changeData)) {
continue;
}
if (Strings.isNullOrEmpty(group.getEmailAddress())) {
matching.list(nc.getHeader()).emails.add(new Address(group.getEmailAddress()));
continue;
}
AccountGroup ig = GroupDescriptions.toAccountGroup(group);
if (ig == null) {
log.warn(String.format(
"Project %s has invalid group %s in notify section %s",
project.get(), ref.getName(), nc.getName()));
continue;
}
if (ig.getType() != AccountGroup.Type.INTERNAL) {
log.warn(String.format(
"Project %s cannot use group %s of type %s in notify section %s",
project.get(), ref.getName(), ig.getType(), nc.getName()));
continue;
}
recursivelyAddAllAccounts(matching.list(nc.getHeader()), ig);
}
if (!nc.getAddresses().isEmpty()) {