Add direct members and directly included groups to GroupInfo

When listing groups it is now possible to request that the direct group
members and the directly included groups are included into the returned
GroupInfos. This is done by setting the options 'o=MEMBERS' and
'o=INCLUDES'.

In addition there is a new REST endpoint '/groups/<group-id>/detail' by
which a group with its direct members and directly included groups can
be retrieved.

This new endpoint is used to retrieve the group for populating the
AccountGroupMembersScreen. By this 2 requests are saved which would
otherwise be needed to fetch the direct group members and the directly
included groups.

Change-Id: Ib94e5c01cc0880aa508f703f4156ac7fe627d742
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-02-11 15:30:19 +01:00
parent 963dfd07d0
commit abaab54633
18 changed files with 331 additions and 55 deletions

View File

@@ -107,6 +107,23 @@ by group name.
get::/groups/
****
[[group-options]]
Group Options
^^^^^^^^^^^^^
Additional fields can be obtained by adding `o` parameters, each option
requires more lookups and slows down the query response time to the
client so they are generally disabled by default. Optional fields are:
[[includes]]
--
* `INCLUDES`: include list of directly included groups.
--
[[members]]
--
* `MEMBERS`: include list of direct group members.
--
Check if a group is owned by the calling user
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By setting the option `owned` and specifying a group to inspect with
@@ -229,6 +246,60 @@ describes the created group.
If the group creation fails because the name is already in use the
response is "`409 Conflict`".
[[get-group-detail]]
GET /groups/\{group-id\}/detail (Get Group Detail)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Retrieves a group with the direct link:#members[members] and the
directly link:#includes[included groups].
.Request
----
GET /groups/6a1e70e1a88782771a91808c8af9bbb7a9871389/detail HTTP/1.0
----
As response a link:#group-info[GroupInfo] entity is returned that
describes the group.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
{
"kind": "gerritcodereview#group",
"id": "6a1e70e1a88782771a91808c8af9bbb7a9871389",
"name": "Administrators",
"url": "#/admin/groups/uuid-6a1e70e1a88782771a91808c8af9bbb7a9871389",
"options": {
},
"description": "Gerrit Site Administrators",
"group_id": 1,
"owner": "Administrators",
"owner_id": "6a1e70e1a88782771a91808c8af9bbb7a9871389",
"members": [
{
"kind": "gerritcodereview#member",
"full_name": "Jane Roe",
"id": "1000097",
"account_id": 1000097,
"preferred_email": "jane.roe@example.com",
"user_name": "jane"
},
{
"kind": "gerritcodereview#member",
"full_name": "John Doe",
"id": "1000096",
"account_id": 1000096,
"preferred_email": "john.doe@example.com",
"user_name": "doe"
}
],
"includes": []
}
----
[[get-group-name]]
GET /groups/\{group-id\}/name (Get Group Name)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -989,6 +1060,14 @@ permits users to apply to join the group, or manage their membership.
|`group_id` |only for internal groups|The numeric ID of the group.
|`owner` |only for internal groups|The name of the owner group.
|`owner_id` |only for internal groups|The URL encoded UUID of the owner group.
|`members` |optional, only for internal groups|
A list of link:rest-api-accounts.html#account-info[AccountInfo]
entities describing the direct members. +
Only set if link:#members[members] are requested.
|`includes` |optional, only for internal groups|
A list of link:#group-info[GroupInfo] entities describing the directly
included groups. +
Only set if link:#includes[included groups] are requested.
|===========================
The type of a group can be deduced from the group's UUID:

View File

@@ -0,0 +1,66 @@
// Copyright (C) 2013 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.common.groups;
import java.util.EnumSet;
/** Output options available when using {@code /groups/} RPCs. */
public enum ListGroupsOption {
/** Return information on the direct group members. */
MEMBERS(0),
/** Return information on the directly included groups. */
INCLUDES(1);
private final int value;
private ListGroupsOption(int v) {
this.value = v;
}
public int getValue() {
return value;
}
public static ListGroupsOption fromValue(int value) {
return ListGroupsOption.values()[value];
}
public static EnumSet<ListGroupsOption> fromBits(int v) {
EnumSet<ListGroupsOption> r = EnumSet.noneOf(ListGroupsOption.class);
for (ListGroupsOption o : ListGroupsOption.values()) {
if ((v & (1 << o.value)) != 0) {
r.add(o);
v &= ~(1 << o.value);
}
if (v == 0) {
return r;
}
}
if (v != 0) {
throw new IllegalArgumentException("unknown " + Integer.toHexString(v));
}
return r;
}
public static int toBits(EnumSet<ListGroupsOption> set) {
int r = 0;
for (ListGroupsOption o : set) {
r |= 1 << o.value;
}
return r;
}
}

View File

@@ -756,7 +756,7 @@ public class Dispatcher {
return;
}
GroupApi.getGroup(group, new GerritCallback<GroupInfo>() {
GroupApi.getGroupDetail(group, new GerritCallback<GroupInfo>() {
@Override
public void onSuccess(GroupInfo group) {
if (panel == null || panel.isEmpty()) {

View File

@@ -20,8 +20,6 @@ import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.groups.GroupApi;
import com.google.gerrit.client.groups.GroupInfo;
import com.google.gerrit.client.groups.GroupList;
import com.google.gerrit.client.groups.MemberList;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
@@ -152,18 +150,8 @@ public class AccountGroupMembersScreen extends AccountGroupScreen {
protected void display(final GroupInfo group, final boolean canModify) {
if (AccountGroup.isInternalGroup(group.getGroupUUID())
&& !AccountGroup.isSystemGroup(group.getGroupUUID())) {
MemberList.all(getGroupUUID(), new GerritCallback<MemberList>() {
@Override
public void onSuccess(MemberList result) {
members.display(Natives.asList(result));
}
});
GroupList.included(getGroupUUID(), new GerritCallback<GroupList>() {
@Override
public void onSuccess(GroupList result) {
includes.display(Natives.asList(result));
}
});
members.display(Natives.asList(group.members()));
includes.display(Natives.asList(group.includes()));
} else {
memberPanel.setVisible(false);
includePanel.setVisible(false);

View File

@@ -37,8 +37,8 @@ public class GroupApi {
new RestApi("/groups/").id(groupName).ifNoneMatch().put(in, cb);
}
public static void getGroup(String group, AsyncCallback<GroupInfo> cb) {
group(group).get(cb);
public static void getGroupDetail(String group, AsyncCallback<GroupInfo> cb) {
group(group).view("detail").get(cb);
}
/** Get the name of a group */

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.client.groups;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.http.client.URL;
public class GroupInfo extends JavaScriptObject {
@@ -34,6 +36,8 @@ public class GroupInfo extends JavaScriptObject {
public final native String url() /*-{ return this.url; }-*/;
public final native String owner() /*-{ return this.owner; }-*/;
public final native void owner(String o) /*-{ if(o)this.owner=o; }-*/;
public final native JsArray<AccountInfo> members() /*-{ return this.members; }-*/;
public final native JsArray<GroupInfo> includes() /*-{ return this.includes; }-*/;
private final native int group_id() /*-{ return this.group_id; }-*/;
private final native String owner_id() /*-{ return this.owner_id; }-*/;

View File

@@ -16,12 +16,14 @@ package com.google.gerrit.server.account;
import com.google.common.collect.Lists;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.group.GroupJson;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.util.List;
@@ -37,7 +39,8 @@ public class GetGroups implements RestReadView<AccountResource> {
}
@Override
public List<GroupInfo> apply(AccountResource resource) {
public List<GroupInfo> apply(AccountResource resource)
throws ResourceNotFoundException, OrmException {
IdentifiedUser user = resource.getUser();
Account.Id userId = user.getAccountId();
List<GroupInfo> groups = Lists.newArrayList();

View File

@@ -82,8 +82,8 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
@Override
public List<GroupInfo> apply(GroupResource resource, Input input)
throws MethodNotAllowedException, AuthException, BadRequestException,
OrmException {
throws ResourceNotFoundException, MethodNotAllowedException,
AuthException, BadRequestException, OrmException {
AccountGroup group = resource.toAccountGroup();
if (group == null) {
throw new MethodNotAllowedException();
@@ -153,8 +153,8 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
@Override
public GroupInfo apply(GroupResource resource, Input input)
throws MethodNotAllowedException, AuthException, BadRequestException,
OrmException {
throws ResourceNotFoundException, MethodNotAllowedException,
AuthException, BadRequestException, OrmException {
AddIncludedGroups.Input in = new AddIncludedGroups.Input();
in.groups = ImmutableList.of(id);
List<GroupInfo> list = put.get().apply(resource, in);
@@ -177,7 +177,9 @@ public class AddIncludedGroups implements RestModifyView<GroupResource, Input> {
}
@Override
public Object apply(IncludedGroupResource resource, PutIncludedGroup.Input input) {
public Object apply(IncludedGroupResource resource,
PutIncludedGroup.Input input) throws ResourceNotFoundException,
OrmException {
// Do nothing, the group is already included.
return get.get().apply(resource);
}

View File

@@ -78,8 +78,8 @@ class CreateGroup implements RestModifyView<TopLevelResource, Input> {
@Override
public GroupInfo apply(TopLevelResource resource, Input input)
throws AuthException, BadRequestException, OrmException,
NameAlreadyUsedException {
throws ResourceNotFoundException, AuthException, BadRequestException,
OrmException, NameAlreadyUsedException {
if (input == null) {
input = new Input();
}

View File

@@ -0,0 +1,38 @@
// Copyright (C) 2013 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.server.group;
import com.google.gerrit.common.groups.ListGroupsOption;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
public class GetDetail implements RestReadView<GroupResource> {
private final GroupJson json;
@Inject
GetDetail(GroupJson json) {
this.json = json.addOption(ListGroupsOption.MEMBERS)
.addOption(ListGroupsOption.INCLUDES);
}
@Override
public GroupInfo apply(GroupResource rsrc) throws ResourceNotFoundException,
OrmException {
return json.format(rsrc);
}
}

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.server.group;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
class GetGroup implements RestReadView<GroupResource> {
@@ -27,7 +29,8 @@ class GetGroup implements RestReadView<GroupResource> {
}
@Override
public GroupInfo apply(GroupResource resource) {
public GroupInfo apply(GroupResource resource)
throws ResourceNotFoundException, OrmException {
return json.format(resource.getGroup());
}
}

View File

@@ -14,8 +14,10 @@
package com.google.gerrit.server.group;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
public class GetIncludedGroup implements RestReadView<IncludedGroupResource> {
@@ -27,7 +29,8 @@ public class GetIncludedGroup implements RestReadView<IncludedGroupResource> {
}
@Override
public GroupInfo apply(IncludedGroupResource rsrc) {
public GroupInfo apply(IncludedGroupResource rsrc)
throws ResourceNotFoundException, OrmException {
return json.format(rsrc.getMemberDescription());
}
}

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
public class GetOwner implements RestReadView<GroupResource> {
@@ -34,7 +35,8 @@ public class GetOwner implements RestReadView<GroupResource> {
}
@Override
public GroupInfo apply(GroupResource resource) throws ResourceNotFoundException {
public GroupInfo apply(GroupResource resource)
throws ResourceNotFoundException, OrmException {
AccountGroup group = resource.toAccountGroup();
if (group == null) {
throw new ResourceNotFoundException();

View File

@@ -14,23 +14,75 @@
package com.google.gerrit.server.group;
import static com.google.gerrit.common.groups.ListGroupsOption.INCLUDES;
import static com.google.gerrit.common.groups.ListGroupsOption.MEMBERS;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.groups.ListGroupsOption;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class GroupJson {
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final Provider<ListMembers> listMembers;
private final Provider<ListIncludedGroups> listIncludes;
private EnumSet<ListGroupsOption> options;
@Inject
GroupJson(GroupCache groupCache) {
GroupJson(GroupCache groupCache, GroupControl.Factory groupControlFactory,
Provider<ListMembers> listMembers,
Provider<ListIncludedGroups> listIncludes) {
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.listMembers = listMembers;
this.listIncludes = listIncludes;
options = EnumSet.noneOf(ListGroupsOption.class);
}
public GroupInfo format(GroupDescription.Basic group) {
public GroupJson addOption(ListGroupsOption o) {
options.add(o);
return this;
}
public GroupJson addOptions(Collection<ListGroupsOption> o) {
options.addAll(o);
return this;
}
public GroupInfo format(GroupResource rsrc) throws ResourceNotFoundException,
OrmException {
GroupInfo info = init(rsrc.getGroup());
initMembersAndIncludes(rsrc, info);
return info;
}
public GroupInfo format(GroupDescription.Basic group)
throws ResourceNotFoundException, OrmException {
GroupInfo info = init(group);
if (options.contains(MEMBERS) || options.contains(INCLUDES)) {
GroupResource rsrc =
new GroupResource(groupControlFactory.controlFor(group));
initMembersAndIncludes(rsrc, info);
}
return info;
}
private GroupInfo init(GroupDescription.Basic group) {
GroupInfo info = new GroupInfo();
info.id = Url.encode(group.getGroupUUID().get());
info.name = Strings.emptyToNull(group.getName());
@@ -53,6 +105,18 @@ public class GroupJson {
return info;
}
private GroupInfo initMembersAndIncludes(GroupResource rsrc, GroupInfo info)
throws ResourceNotFoundException, OrmException {
if (options.contains(MEMBERS)) {
info.members = listMembers.get().apply(rsrc);
}
if (options.contains(INCLUDES)) {
info.includes = listIncludes.get().apply(rsrc);
}
return info;
}
public static class GroupInfo {
final String kind = "gerritcodereview#group";
public String id;
@@ -65,5 +129,9 @@ public class GroupJson {
public Integer groupId;
public String owner;
public String ownerId;
// These fields are only supplied for internal groups, but only if requested
public List<AccountInfo> members;
public List<GroupInfo> includes;
}
}

View File

@@ -22,9 +22,11 @@ import com.google.common.collect.Sets;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.common.groups.ListGroupsOption;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
@@ -40,6 +42,7 @@ import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -47,6 +50,7 @@ import org.kohsuke.args4j.Option;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -63,6 +67,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<GetGroups> accountGetGroups;
private final GroupJson json;
private EnumSet<ListGroupsOption> options;
@Option(name = "--project", aliases = {"-p"},
usage = "projects for which the groups should be listed")
@@ -92,6 +97,16 @@ public class ListGroups implements RestReadView<TopLevelResource> {
@Option(name = "-m", metaVar = "MATCH", usage = "match group substring")
private String matchSubstring;
@Option(name = "-o", multiValued = true, usage = "Output options per group")
public void addOption(ListGroupsOption o) {
options.add(o);
}
@Option(name = "-O", usage = "Output option flags, in hex")
void setOptionFlagsHex(String hex) {
options.addAll(ListGroupsOption.fromBits(Integer.parseInt(hex, 16)));
}
@Inject
protected ListGroups(final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
@@ -106,6 +121,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
this.userFactory = userFactory;
this.accountGetGroups = accountGetGroups;
this.json = json;
this.options = EnumSet.noneOf(ListGroupsOption.class);
}
public Account.Id getUser() {
@@ -130,7 +146,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
new TypeToken<Map<String, GroupInfo>>() {}.getType());
}
public List<GroupInfo> get() throws NoSuchGroupException {
public List<GroupInfo> get() throws ResourceNotFoundException, OrmException {
List<GroupInfo> groupInfos;
if (user != null) {
if (owned) {
@@ -151,7 +167,7 @@ public class ListGroups implements RestReadView<TopLevelResource> {
for (final GroupReference groupRef : groupsRefs) {
final AccountGroup group = groupCache.get(groupRef.getUUID());
if (group == null) {
throw new NoSuchGroupException(groupRef.getUUID());
throw new ResourceNotFoundException(groupRef.getUUID().get());
}
groups.put(group.getGroupUUID(), group);
}
@@ -162,21 +178,23 @@ public class ListGroups implements RestReadView<TopLevelResource> {
}
groupInfos = Lists.newArrayListWithCapacity(groupList.size());
for (AccountGroup group : groupList) {
groupInfos.add(json.format(GroupDescriptions.forAccountGroup(group)));
groupInfos.add(json.addOptions(options).format(
GroupDescriptions.forAccountGroup(group)));
}
}
}
return groupInfos;
}
private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) {
private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user)
throws ResourceNotFoundException, OrmException {
List<GroupInfo> groups = Lists.newArrayList();
for (AccountGroup g : filterGroups(groupCache.all())) {
GroupControl ctl = groupControlFactory.controlFor(g);
try {
if (genericGroupControlFactory.controlFor(user, g.getGroupUUID())
.isOwner()) {
groups.add(json.format(ctl.getGroup()));
groups.add(json.addOptions(options).format(ctl.getGroup()));
}
} catch (NoSuchGroupException e) {
continue;

View File

@@ -20,9 +20,6 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
@@ -62,24 +59,27 @@ public class ListMembers implements RestReadView<GroupResource> {
@Override
public List<AccountInfo> apply(final GroupResource resource)
throws AuthException, BadRequestException, ResourceConflictException,
ResourceNotFoundException, Exception {
throws ResourceNotFoundException, OrmException {
if (resource.toAccountGroup() == null) {
throw new ResourceNotFoundException(resource.getGroupUUID().get());
}
final Map<Account.Id, AccountInfo> members =
getMembers(resource.getGroupUUID(), new HashSet<AccountGroup.UUID>());
final List<AccountInfo> memberInfos = Lists.newArrayList(members.values());
Collections.sort(memberInfos, new Comparator<AccountInfo>() {
@Override
public int compare(AccountInfo a, AccountInfo b) {
return ComparisonChain.start()
.compare(a.name, b.name, Ordering.natural().nullsFirst())
.compare(a.email, b.email, Ordering.natural().nullsFirst())
.compare(a._account_id, b._account_id, Ordering.natural().nullsFirst()).result();
}
});
return memberInfos;
try {
final Map<Account.Id, AccountInfo> members =
getMembers(resource.getGroupUUID(), new HashSet<AccountGroup.UUID>());
final List<AccountInfo> memberInfos = Lists.newArrayList(members.values());
Collections.sort(memberInfos, new Comparator<AccountInfo>() {
@Override
public int compare(AccountInfo a, AccountInfo b) {
return ComparisonChain.start()
.compare(a.name, b.name, Ordering.natural().nullsFirst())
.compare(a.email, b.email, Ordering.natural().nullsFirst())
.compare(a._account_id, b._account_id, Ordering.natural().nullsFirst()).result();
}
});
return memberInfos;
} catch (NoSuchGroupException e) {
throw new ResourceNotFoundException(resource.getGroupUUID().get());
}
}
private Map<Account.Id, AccountInfo> getMembers(

View File

@@ -37,6 +37,7 @@ public class Module extends RestApiModule {
get(GROUP_KIND).to(GetGroup.class);
put(GROUP_KIND).to(PutGroup.class);
get(GROUP_KIND, "detail").to(GetDetail.class);
post(GROUP_KIND, "members").to(AddMembers.class);
post(GROUP_KIND, "members.add").to(AddMembers.class);
post(GROUP_KIND, "members.delete").to(DeleteMembers.class);

View File

@@ -16,7 +16,7 @@ package com.google.gerrit.sshd.commands;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
@@ -29,6 +29,7 @@ import com.google.gerrit.server.group.ListGroups;
import com.google.gerrit.server.ioutil.ColumnFormatter;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -80,7 +81,7 @@ public class ListGroupsCommand extends BaseCommand {
identifiedUser, userFactory, accountGetGroups, json);
}
void display(final PrintWriter out) throws NoSuchGroupException {
void display(final PrintWriter out) throws ResourceNotFoundException, OrmException {
final ColumnFormatter formatter = new ColumnFormatter(out, '\t');
for (final GroupInfo info : get()) {
formatter.addColumn(Objects.firstNonNull(info.name, "n/a"));