diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt index afdf36d53d..e0df4ca28a 100644 --- a/Documentation/rest-api-groups.txt +++ b/Documentation/rest-api-groups.txt @@ -172,6 +172,46 @@ Query 25 groups starting from index 50. GET /groups/?n=25&S=50 HTTP/1.0 ---- +[[suggest-group]] +==== Suggest Group +The `suggest` option indicates a user-entered string that +should be auto-completed to group names. +If this option is set and `n` is not set, then `n` defaults to 10. + +When using this option, +the `project` or `p` option can be used to name the current project, +to allow context-dependent suggestions. + +Not compatible with `visible-to-all`, `owned`, `user`, `match`, `q`, +or `S`. +(Attempts to use one of those options combined with `suggest` will +error out.) + +.Request +---- + GET /groups/?suggest=ad&p=All-Projects HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + { + "Administrators": { + "url": "#/admin/groups/uuid-59b92f35489e62c80d1ab1bf0c2d17843038df8b", + "options": {}, + "description": "Gerrit Site Administrators", + "group_id": 1, + "owner": "Administrators", + "owner_id": "59b92f35489e62c80d1ab1bf0c2d17843038df8b", + "id": "59b92f35489e62c80d1ab1bf0c2d17843038df8b" + } + } +---- + [[get-group]] === Get Group -- diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java index 5b8b87f4d4..c72edd71dc 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java @@ -401,6 +401,13 @@ public class GroupsIT extends AbstractDaemonTest { assertThat(gApi.groups().list().getAsMap()).containsKey(newGroupName); } + @Test + public void testSuggestGroup() throws Exception { + Map groups = gApi.groups().list().withSuggest("adm").getAsMap(); + assertThat(groups).containsKey("Administrators"); + assertThat(groups).hasSize(1); + } + @Test public void testAllGroupInfoFieldsSetCorrectly() throws Exception { AccountGroup adminGroup = getFromCache("Administrators"); diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java index ab09e5f0f7..b909f317da 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java @@ -63,6 +63,7 @@ public interface Groups { private int limit; private int start; private String substring; + private String suggest; public List get() throws RestApiException { Map map = getAsMap(); @@ -128,6 +129,11 @@ public interface Groups { return this; } + public ListRequest withSuggest(String suggest) { + this.suggest = suggest; + return this; + } + public EnumSet getOptions() { return options; } @@ -163,5 +169,9 @@ public interface Groups { public String getSubstring() { return substring; } + + public String getSuggest() { + return suggest; + } } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java index 97ba5d3274..3d2c960de4 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java @@ -138,6 +138,7 @@ class GroupsImpl implements Groups { list.setLimit(req.getLimit()); list.setStart(req.getStart()); list.setMatchSubstring(req.getSubstring()); + list.setSuggest(req.getSuggest()); try { return list.apply(tlr); } catch (OrmException e) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java index bf5193f83a..023e3d40ba 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java @@ -16,14 +16,18 @@ package com.google.gerrit.server.group; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import com.google.gerrit.common.Nullable; +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.NoSuchGroupException; import com.google.gerrit.extensions.client.ListGroupsOption; import com.google.gerrit.extensions.common.GroupInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.Url; @@ -32,6 +36,7 @@ import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.GetGroups; +import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupComparator; import com.google.gerrit.server.account.GroupControl; @@ -64,6 +69,7 @@ public class ListGroups implements RestReadView { private final IdentifiedUser.GenericFactory userFactory; private final Provider accountGetGroups; private final GroupJson json; + private final GroupBackend groupBackend; private EnumSet options = EnumSet.noneOf(ListGroupsOption.class); @@ -73,6 +79,7 @@ public class ListGroups implements RestReadView { private int limit; private int start; private String matchSubstring; + private String suggest; @Option(name = "--project", aliases = {"-p"}, usage = "projects for which the groups should be listed") @@ -121,6 +128,11 @@ public class ListGroups implements RestReadView { this.matchSubstring = matchSubstring; } + @Option(name = "--suggest", usage = "to get a suggestion of groups") + public void setSuggest(String suggest) { + this.suggest = suggest; + } + @Option(name = "-o", usage = "Output options per group") void addOption(ListGroupsOption o) { options.add(o); @@ -137,7 +149,8 @@ public class ListGroups implements RestReadView { final GroupControl.GenericFactory genericGroupControlFactory, final Provider identifiedUser, final IdentifiedUser.GenericFactory userFactory, - final Provider accountGetGroups, GroupJson json) { + final Provider accountGetGroups, GroupJson json, + GroupBackend groupBackend) { this.groupCache = groupCache; this.groupControlFactory = groupControlFactory; this.genericGroupControlFactory = genericGroupControlFactory; @@ -145,6 +158,7 @@ public class ListGroups implements RestReadView { this.userFactory = userFactory; this.accountGetGroups = accountGetGroups; this.json = json; + this.groupBackend = groupBackend; } public void setOptions(EnumSet options) { @@ -161,7 +175,7 @@ public class ListGroups implements RestReadView { @Override public SortedMap apply(TopLevelResource resource) - throws OrmException { + throws OrmException, BadRequestException { SortedMap output = Maps.newTreeMap(); for (GroupInfo info : get()) { output.put(MoreObjects.firstNonNull( @@ -172,53 +186,107 @@ public class ListGroups implements RestReadView { return output; } - public List get() throws OrmException { - List groupInfos; + public List get() throws OrmException, BadRequestException { + if (!Strings.isNullOrEmpty(suggest)) { + return suggestGroups(); + } + + if (owned) { + return getGroupsOwnedBy( + user != null ? userFactory.create(user) : identifiedUser.get()); + } + if (user != null) { - if (owned) { - groupInfos = getGroupsOwnedBy(userFactory.create(user)); - } else { - groupInfos = accountGetGroups.get().apply( - new AccountResource(userFactory.create(user))); + return accountGetGroups.get().apply( + new AccountResource(userFactory.create(user))); + } + + return getAllGroups(); + } + + private List getAllGroups() throws OrmException { + List groupInfos; + List groupList; + if (!projects.isEmpty()) { + Map groups = Maps.newHashMap(); + for (final ProjectControl projectControl : projects) { + final Set groupsRefs = projectControl.getAllGroups(); + for (final GroupReference groupRef : groupsRefs) { + final AccountGroup group = groupCache.get(groupRef.getUUID()); + if (group != null) { + groups.put(group.getGroupUUID(), group); + } + } } + groupList = filterGroups(groups.values()); } else { - if (owned) { - groupInfos = getGroupsOwnedBy(identifiedUser.get()); - } else { - List groupList; - if (!projects.isEmpty()) { - Map groups = Maps.newHashMap(); - for (final ProjectControl projectControl : projects) { - final Set groupsRefs = projectControl.getAllGroups(); - for (final GroupReference groupRef : groupsRefs) { - final AccountGroup group = groupCache.get(groupRef.getUUID()); - if (group != null) { - groups.put(group.getGroupUUID(), group); - } - } - } - groupList = filterGroups(groups.values()); - } else { - groupList = filterGroups(groupCache.all()); - } - groupInfos = Lists.newArrayListWithCapacity(groupList.size()); - int found = 0; - int foundIndex = 0; - for (AccountGroup group : groupList) { - if (foundIndex++ < start) { - continue; - } - if (limit > 0 && ++found > limit) { - break; - } - groupInfos.add(json.addOptions(options).format( - GroupDescriptions.forAccountGroup(group))); - } + groupList = filterGroups(groupCache.all()); + } + groupInfos = Lists.newArrayListWithCapacity(groupList.size()); + int found = 0; + int foundIndex = 0; + for (AccountGroup group : groupList) { + if (foundIndex++ < start) { + continue; + } + if (limit > 0 && ++found > limit) { + break; + } + groupInfos.add(json.addOptions(options).format( + GroupDescriptions.forAccountGroup(group))); + } + return groupInfos; + } + + private List suggestGroups() throws OrmException, BadRequestException { + if (conflictingSuggestParameters()) { + throw new BadRequestException( + "You should only have no more than one --project and -n with --suggest"); + } + + List groupRefs = Lists.newArrayList(Iterables.limit( + groupBackend.suggest( + suggest, Iterables.getFirst(projects, null)), + limit <= 0 ? 10 : Math.min(limit, 10))); + + List groupInfos = Lists.newArrayListWithCapacity(groupRefs.size()); + for (final GroupReference ref : groupRefs) { + GroupDescription.Basic desc = groupBackend.get(ref.getUUID()); + if (desc != null) { + groupInfos.add(json.addOptions(options).format(desc)); } } return groupInfos; } + private boolean conflictingSuggestParameters() { + if (Strings.isNullOrEmpty(suggest)) { + return false; + } + if (projects.size() > 1) { + return true; + } + if (visibleToAll) { + return true; + } + if (user != null) { + return true; + } + if (owned) { + return true; + } + if (start != 0) { + return true; + } + if (!groupsToInspect.isEmpty()) { + return true; + } + if (!Strings.isNullOrEmpty(matchSubstring)) { + return true; + } + return false; + } + private List getGroupsOwnedBy(IdentifiedUser user) throws OrmException { List groups = Lists.newArrayList(); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java index ad72b13810..75072e860d 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java @@ -19,10 +19,12 @@ import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.gerrit.extensions.common.GroupInfo; +import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.account.GetGroups; +import com.google.gerrit.server.account.GroupBackend; import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.group.GroupJson; @@ -71,12 +73,13 @@ public class ListGroupsCommand extends SshCommand { final Provider identifiedUser, final IdentifiedUser.GenericFactory userFactory, final Provider accountGetGroups, - final GroupJson json) { + final GroupJson json, + GroupBackend groupBackend) { super(groupCache, groupControlFactory, genericGroupControlFactory, - identifiedUser, userFactory, accountGetGroups, json); + identifiedUser, userFactory, accountGetGroups, json, groupBackend); } - void display(final PrintWriter out) throws OrmException { + void display(final PrintWriter out) throws OrmException, BadRequestException { final ColumnFormatter formatter = new ColumnFormatter(out, '\t'); for (final GroupInfo info : get()) { formatter.addColumn(MoreObjects.firstNonNull(info.name, "n/a"));