diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt index 73bb72d2a7..c869c55933 100644 --- a/Documentation/rest-api-accounts.txt +++ b/Documentation/rest-api-accounts.txt @@ -7,19 +7,56 @@ link:rest-api.html[REST API]. [[account-endpoints]] == Account Endpoints -[[suggest-account]] -=== Suggest Account +[[query-account]] +=== Query Account -- 'GET /accounts/' -- -Suggest users for a given query `q` and result limit `n`. If result -limit is not passed, then the default 10 is used. Returns a list of -matching link:#account-info[AccountInfo] entities. +Queries accounts visible to the caller. The +link:user-search-accounts.html#_search_operators[query string] must be +provided by the `q` parameter. The `n` parameter can be used to limit +the returned results. + +As result a list of link:#account-info[AccountInfo] entities is +returned. .Request ---- - GET /accounts/?q=John HTTP/1.0 + GET /accounts/?q=name:John+email:example.com HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + [ + { + "_account_id": 1000096, + "name": "John Doe", + "email": "john.doe@example.com", + "username": "john" + }, + { + "_account_id": 1001439, + "name": "John Smith", + "email": "john.smith@example.com", + "username": "jsmith" + } + ] +---- + +[[suggest-account]] +To get account suggestions set the parameter `suggest` and provide the +typed substring as query `q`. If a result limit `n` is not specified, +then the default 10 is used. + +.Request +---- + GET /accounts/?suggest&q=John HTTP/1.0 ---- .Response diff --git a/Documentation/user-search-accounts.txt b/Documentation/user-search-accounts.txt new file mode 100644 index 0000000000..15d87b05a9 --- /dev/null +++ b/Documentation/user-search-accounts.txt @@ -0,0 +1,83 @@ += Gerrit Code Review - Searching Accounts + +== Basic Change Search + +Similar to many popular search engines on the web, just enter some +text and let Gerrit figure out the meaning: + +[options="header"] +|============================================================= +|Description | Examples +|Name | John +|Email address | jdoe@example.com +|Username | jdoe +|Account-Id | 1000096 +|Own account | self +|============================================================= + +[[search-operators]] +== Search Operators + +Operators act as restrictions on the search. As more operators +are added to the same query string, they further restrict the +returned results. Search can also be performed by typing only a +text with no operator, which will match against a variety of fields. + +[[email]] +email:'EMAIL':: ++ +Matches accounts that have the email address 'EMAIL' or an email +address that starts with 'EMAIL'. + +[[is]] +[[is-active]] +is:active:: ++ +Matches accounts that are active. + +[[is-inactive]] +is:inactive:: ++ +Matches accounts that are inactive. + +[[name]] +name:'NAME':: ++ +Matches accounts that have any name part 'NAME'. The name parts consist +of any part of the full name and the email addresses. + +[[username]] +username:'USERNAME':: ++ +Matches accounts that have the username 'USERNAME'. + +== Magical Operators + +[[is-visible]] +is:visible:: ++ +Magical internal flag to prove the current user has access to read +the change. This flag is always added to any query. + +[[is-active-magic]] +is:active:: ++ +Matches accounts that are active. If neither link:#is-active[is:active] +nor link:#is-inactive[is:inactive] is contained in a query, `is:active` +is automatically added so that by default only active accounts are +matched. + +[[limit]] +limit:'CNT':: ++ +Limit the returned results to no more than 'CNT' records. This is +automatically set to the page size configured in the current user's +preferences. Including it in a web query may lead to unpredictable +results with regards to pagination. + +GERRIT +------ +Part of link:index.html[Gerrit Code Review] + +SEARCHBOX +--------- diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java index a822cfc355..acd2e78475 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountApi.java @@ -53,6 +53,7 @@ public class AccountApi { public static void suggest(String query, int limit, AsyncCallback> cb) { new RestApi("/accounts/") + .addParameterTrue("suggest") .addParameter("q", query) .addParameter("n", limit) .background() diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java index d54a99938a..06cf255db3 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java @@ -40,7 +40,7 @@ public class AccountsCollection implements private final AccountResolver resolver; private final AccountControl.Factory accountControlFactory; private final IdentifiedUser.GenericFactory userFactory; - private final Provider list; + private final Provider list; private final DynamicMap> views; private final CreateAccount.Factory createAccountFactory; @@ -49,7 +49,7 @@ public class AccountsCollection implements AccountResolver resolver, AccountControl.Factory accountControlFactory, IdentifiedUser.GenericFactory userFactory, - Provider list, + Provider list, DynamicMap> views, CreateAccount.Factory createAccountFactory) { this.self = self; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java similarity index 75% rename from gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java rename to gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java index 3b928d673b..68f794e5ca 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/SuggestAccounts.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java @@ -26,7 +26,9 @@ import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.api.accounts.AccountInfoComparator; import com.google.gerrit.server.config.GerritServerConfig; +import com.google.gerrit.server.index.account.AccountIndex; import com.google.gerrit.server.index.account.AccountIndexCollection; +import com.google.gerrit.server.query.Predicate; import com.google.gerrit.server.query.QueryParseException; import com.google.gerrit.server.query.QueryResult; import com.google.gerrit.server.query.account.AccountQueryBuilder; @@ -44,8 +46,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -public class SuggestAccounts implements RestReadView { - private static final int MAX_RESULTS = 100; +public class QueryAccounts implements RestReadView { + private static final int MAX_SUGGEST_RESULTS = 100; private static final String MAX_SUFFIX = "\u9fa5"; private final AccountControl accountControl; @@ -55,22 +57,29 @@ public class SuggestAccounts implements RestReadView { private final AccountQueryBuilder queryBuilder; private final AccountQueryProcessor queryProcessor; private final ReviewDb db; - private final boolean suggest; + private final boolean suggestConfig; private final int suggestFrom; - private int limit = 10; + private boolean suggest; + private int suggestLimit = 10; private String query; + @Option(name = "--suggest", metaVar = "SUGGEST", usage = "suggest users") + public void setSuggest(boolean suggest) { + this.suggest = suggest; + } + @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of users to return") public void setLimit(int n) { + queryProcessor.setLimit(n); + if (n < 0) { - limit = 10; + suggestLimit = 10; } else if (n == 0) { - limit = MAX_RESULTS; + suggestLimit = MAX_SUGGEST_RESULTS; } else { - limit = Math.min(n, MAX_RESULTS); + suggestLimit = Math.min(n, MAX_SUGGEST_RESULTS); } - queryProcessor.setLimit(limit); } @Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "match users") @@ -79,7 +88,7 @@ public class SuggestAccounts implements RestReadView { } @Inject - SuggestAccounts(AccountControl.Factory accountControlFactory, + QueryAccounts(AccountControl.Factory accountControlFactory, AccountLoader.Factory accountLoaderFactory, AccountCache accountCache, AccountIndexCollection indexes, @@ -97,7 +106,7 @@ public class SuggestAccounts implements RestReadView { this.suggestFrom = cfg.getInt("suggest", null, "from", 0); if ("off".equalsIgnoreCase(cfg.getString("suggest", null, "accounts"))) { - suggest = false; + suggestConfig = false; } else { boolean suggest; try { @@ -107,7 +116,7 @@ public class SuggestAccounts implements RestReadView { } catch (IllegalArgumentException err) { suggest = cfg.getBoolean("suggest", null, "accounts", true); } - this.suggest = suggest; + this.suggestConfig = suggest; } } @@ -118,33 +127,49 @@ public class SuggestAccounts implements RestReadView { throw new BadRequestException("missing query field"); } - if (!suggest || query.length() < suggestFrom) { + if (suggest && (!suggestConfig || query.length() < suggestFrom)) { return Collections.emptyList(); } - Collection matches = - indexes.getSearchIndex() != null - ? queryFromIndex() - : queryFromDb(); + AccountIndex searchIndex = indexes.getSearchIndex(); + Collection matches; + if (searchIndex != null) { + matches = queryFromIndex(); + } else { + if (!suggest) { + throw new MethodNotAllowedException(); + } + matches = queryFromDb(); + } + return AccountInfoComparator.ORDER_NULLS_LAST.sortedCopy(matches); } public Collection queryFromIndex() - throws MethodNotAllowedException, OrmException { + throws BadRequestException, MethodNotAllowedException, OrmException { if (queryProcessor.isDisabled()) { throw new MethodNotAllowedException("query disabled"); } Map matches = new LinkedHashMap<>(); try { - QueryResult result = - queryProcessor.query(queryBuilder.defaultField(query)); + Predicate queryPred; + if (suggest) { + queryPred = queryBuilder.defaultField(query); + queryProcessor.setLimit(suggestLimit); + } else { + queryPred = queryBuilder.parse(query); + } + QueryResult result = queryProcessor.query(queryPred); for (AccountState accountState : result.entities()) { Account.Id id = accountState.getAccount().getId(); matches.put(id, accountLoader.get(id)); } } catch (QueryParseException e) { - return ImmutableSet.of(); + if (suggest) { + return ImmutableSet.of(); + } + throw new BadRequestException(e.getMessage()); } accountLoader.fill(); @@ -158,18 +183,18 @@ public class SuggestAccounts implements RestReadView { Map matches = new LinkedHashMap<>(); Map queryEmail = new HashMap<>(); - for (Account p : db.accounts().suggestByFullName(a, b, limit)) { + for (Account p : db.accounts().suggestByFullName(a, b, suggestLimit)) { addSuggestion(matches, p); } - if (matches.size() < limit) { + if (matches.size() < suggestLimit) { for (Account p : db.accounts() - .suggestByPreferredEmail(a, b, limit - matches.size())) { + .suggestByPreferredEmail(a, b, suggestLimit - matches.size())) { addSuggestion(matches, p); } } - if (matches.size() < limit) { + if (matches.size() < suggestLimit) { for (AccountExternalId e : db.accountExternalIds() - .suggestByEmailAddress(a, b, limit - matches.size())) { + .suggestByEmailAddress(a, b, suggestLimit - matches.size())) { if (addSuggestion(matches, e.getAccountId())) { queryEmail.put(e.getAccountId(), e.getEmailAddress()); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java index 3bd7634bfb..6bf65d1eb9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountsImpl.java @@ -24,7 +24,7 @@ import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.AccountsCollection; -import com.google.gerrit.server.account.SuggestAccounts; +import com.google.gerrit.server.account.QueryAccounts; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; @@ -37,17 +37,17 @@ public class AccountsImpl implements Accounts { private final AccountsCollection accounts; private final AccountApiImpl.Factory api; private final Provider self; - private final Provider suggestAccountsProvider; + private final Provider queryAccountsProvider; @Inject AccountsImpl(AccountsCollection accounts, AccountApiImpl.Factory api, Provider self, - Provider suggestAccountsProvider) { + Provider queryAccountsProvider) { this.accounts = accounts; this.api = api; this.self = self; - this.suggestAccountsProvider = suggestAccountsProvider; + this.queryAccountsProvider = queryAccountsProvider; } @Override @@ -92,10 +92,11 @@ public class AccountsImpl implements Accounts { private List suggestAccounts(SuggestAccountsRequest r) throws RestApiException { try { - SuggestAccounts mySuggestAccounts = suggestAccountsProvider.get(); - mySuggestAccounts.setQuery(r.getQuery()); - mySuggestAccounts.setLimit(r.getLimit()); - return mySuggestAccounts.apply(TopLevelResource.INSTANCE); + QueryAccounts myQueryAccounts = queryAccountsProvider.get(); + myQueryAccounts.setSuggest(true); + myQueryAccounts.setQuery(r.getQuery()); + myQueryAccounts.setLimit(r.getLimit()); + return myQueryAccounts.apply(TopLevelResource.INSTANCE); } catch (OrmException e) { throw new RestApiException("Cannot retrieve suggested accounts", e); }