Merge changes I49a9aa11,Icc8c34c3,I31497d0d,I193a77d1,I5b18c6ea, ...
* changes: Account query: Add option to control if details should be returned Set '_more_accounts' on last account of query result QueryAccounts: Support parameter 'start' Test account query with limit Account index: Make query matches case insensitive Add basic account query tests Accounts API: Add query methods Support arbitrary account queries via REST Use account index for suggesting accounts if available
This commit is contained in:
@@ -7,19 +7,71 @@ 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&n=2 HTTP/1.0
|
||||
----
|
||||
|
||||
.Response
|
||||
----
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
)]}'
|
||||
[
|
||||
{
|
||||
"_account_id": 1000096,
|
||||
},
|
||||
{
|
||||
"_account_id": 1001439,
|
||||
"_more_accounts": true
|
||||
}
|
||||
]
|
||||
----
|
||||
|
||||
If the number of accounts matching the query exceeds either the
|
||||
internal limit or a supplied `n` query parameter, the last account
|
||||
object has a `_more_accounts: true` JSON field set.
|
||||
|
||||
The `S` or `start` query parameter can be supplied to skip a number
|
||||
of accounts from the list.
|
||||
|
||||
Additional fields can be obtained by adding `o` parameters, each
|
||||
option slows down the query response time to the client so they are
|
||||
generally disabled by default. Optional fields are:
|
||||
|
||||
[[details]]
|
||||
--
|
||||
* `DETAILS`: Includes full name, preferred email, username and avatars
|
||||
for each account.
|
||||
--
|
||||
|
||||
[[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.
|
||||
|
||||
For account suggestions link:#details[account details] are always
|
||||
returned.
|
||||
|
||||
.Request
|
||||
----
|
||||
GET /accounts/?suggest&q=John HTTP/1.0
|
||||
----
|
||||
|
||||
.Response
|
||||
@@ -1898,20 +1950,29 @@ registered.
|
||||
The `AccountInfo` entity contains information about an account.
|
||||
|
||||
[options="header",cols="1,^1,5"]
|
||||
|===========================
|
||||
|Field Name ||Description
|
||||
|`_account_id` ||The numeric ID of the account.
|
||||
|`name` |optional|The full name of the user. +
|
||||
Only set if link:rest-api-changes.html#detailed-accounts[detailed
|
||||
account information] is requested.
|
||||
|`email` |optional|
|
||||
|=============================
|
||||
|Field Name ||Description
|
||||
|`_account_id` ||The numeric ID of the account.
|
||||
|`name` |optional|The full name of the user. +
|
||||
Only set if detailed account information is requested. +
|
||||
See option link:rest-api-changes.html#detailed-accounts[
|
||||
DETAILED_ACCOUNTS] for change queries +
|
||||
and option link:#detaileds[DETAILS] for account queries.
|
||||
|`email` |optional|
|
||||
The email address the user prefers to be contacted through. +
|
||||
Only set if link:rest-api-changes.html#detailed-accounts[detailed
|
||||
account information] is requested.
|
||||
|`username` |optional|The username of the user. +
|
||||
Only set if link:rest-api-changes.html#detailed-accounts[detailed
|
||||
account information] is requested.
|
||||
|===========================
|
||||
Only set if detailed account information is requested. +
|
||||
See option link:rest-api-changes.html#detailed-accounts[
|
||||
DETAILED_ACCOUNTS] for change queries +
|
||||
and option link:#detaileds[DETAILS] for account queries.
|
||||
|`username` |optional|The username of the user. +
|
||||
Only set if detailed account information is requested. +
|
||||
See option link:rest-api-changes.html#detailed-accounts[
|
||||
DETAILED_ACCOUNTS] for change queries +
|
||||
and option link:#detaileds[DETAILS] for account queries.
|
||||
|`_more_accounts`|optional, not set if `false`|
|
||||
Whether the query would deliver more results if not limited. +
|
||||
Only set on the last account that is returned.
|
||||
|=============================
|
||||
|
||||
[[account-input]]
|
||||
=== AccountInput
|
||||
|
||||
83
Documentation/user-search-accounts.txt
Normal file
83
Documentation/user-search-accounts.txt
Normal file
@@ -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
|
||||
---------
|
||||
@@ -14,10 +14,13 @@
|
||||
|
||||
package com.google.gerrit.extensions.api.accounts;
|
||||
|
||||
import com.google.gerrit.extensions.client.ListAccountsOption;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.restapi.NotImplementedException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public interface Accounts {
|
||||
@@ -69,6 +72,25 @@ public interface Accounts {
|
||||
SuggestAccountsRequest suggestAccounts(String query)
|
||||
throws RestApiException;
|
||||
|
||||
/**
|
||||
* Queries users.
|
||||
* <p>
|
||||
* Example code:
|
||||
* {@code query().withQuery("name:John email:example.com").withLimit(5).get()}
|
||||
*
|
||||
* @return API for setting parameters and getting result.
|
||||
*/
|
||||
QueryRequest query() throws RestApiException;
|
||||
|
||||
/**
|
||||
* Queries users.
|
||||
* <p>
|
||||
* Shortcut API for {@code query().withQuery(String)}.
|
||||
*
|
||||
* @see #query()
|
||||
*/
|
||||
QueryRequest query(String query) throws RestApiException;
|
||||
|
||||
/**
|
||||
* API for setting parameters and getting result.
|
||||
* Used for {@code suggestAccounts()}.
|
||||
@@ -112,6 +134,84 @@ public interface Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API for setting parameters and getting result.
|
||||
* Used for {@code query()}.
|
||||
*
|
||||
* @see #query()
|
||||
*/
|
||||
abstract class QueryRequest {
|
||||
private String query;
|
||||
private int limit;
|
||||
private int start;
|
||||
private EnumSet<ListAccountsOption> options =
|
||||
EnumSet.noneOf(ListAccountsOption.class);
|
||||
|
||||
/**
|
||||
* Executes query and returns a list of accounts.
|
||||
*/
|
||||
public abstract List<AccountInfo> get() throws RestApiException;
|
||||
|
||||
/**
|
||||
* Set query.
|
||||
*
|
||||
* @param query needs to be in human-readable form.
|
||||
*/
|
||||
public QueryRequest withQuery(String query) {
|
||||
this.query = query;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set limit for returned list of accounts.
|
||||
* Optional; server-default is used when not provided.
|
||||
*/
|
||||
public QueryRequest withLimit(int limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set number of accounts to skip.
|
||||
* Optional; no accounts are skipped when not provided.
|
||||
*/
|
||||
public QueryRequest withStart(int start) {
|
||||
this.start = start;
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryRequest withOption(ListAccountsOption options) {
|
||||
this.options.add(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryRequest withOptions(ListAccountsOption... options) {
|
||||
this.options.addAll(Arrays.asList(options));
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryRequest withOptions(EnumSet<ListAccountsOption> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public EnumSet<ListAccountsOption> getOptions() {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A default implementation which allows source compatibility
|
||||
* when adding new methods to the interface.
|
||||
@@ -142,5 +242,15 @@ public interface Accounts {
|
||||
throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryRequest query() throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryRequest query(String query) throws RestApiException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2016 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.extensions.client;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** Output options available for retrieval of account details. */
|
||||
public enum ListAccountsOption {
|
||||
/** Return detailed account properties. */
|
||||
DETAILS(0);
|
||||
|
||||
private final int value;
|
||||
|
||||
ListAccountsOption(int v) {
|
||||
this.value = v;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static EnumSet<ListAccountsOption> fromBits(int v) {
|
||||
EnumSet<ListAccountsOption> r = EnumSet.noneOf(ListAccountsOption.class);
|
||||
for (ListAccountsOption o : ListAccountsOption.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(Set<ListAccountsOption> set) {
|
||||
int r = 0;
|
||||
for (ListAccountsOption o : set) {
|
||||
r |= 1 << o.value;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class AccountInfo {
|
||||
public String email;
|
||||
public String username;
|
||||
public List<AvatarInfo> avatars;
|
||||
public Boolean _moreAccounts;
|
||||
|
||||
public AccountInfo(Integer id) {
|
||||
this._accountId = id;
|
||||
|
||||
@@ -53,6 +53,7 @@ public class AccountApi {
|
||||
public static void suggest(String query, int limit,
|
||||
AsyncCallback<JsArray<AccountInfo>> cb) {
|
||||
new RestApi("/accounts/")
|
||||
.addParameterTrue("suggest")
|
||||
.addParameter("q", query)
|
||||
.addParameter("n", limit)
|
||||
.background()
|
||||
|
||||
@@ -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<SuggestAccounts> list;
|
||||
private final Provider<QueryAccounts> list;
|
||||
private final DynamicMap<RestView<AccountResource>> views;
|
||||
private final CreateAccount.Factory createAccountFactory;
|
||||
|
||||
@@ -49,7 +49,7 @@ public class AccountsCollection implements
|
||||
AccountResolver resolver,
|
||||
AccountControl.Factory accountControlFactory,
|
||||
IdentifiedUser.GenericFactory userFactory,
|
||||
Provider<SuggestAccounts> list,
|
||||
Provider<QueryAccounts> list,
|
||||
DynamicMap<RestView<AccountResource>> views,
|
||||
CreateAccount.Factory createAccountFactory) {
|
||||
this.self = self;
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
// Copyright (C) 2014 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.account;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gerrit.extensions.client.ListAccountsOption;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.RestReadView;
|
||||
import com.google.gerrit.extensions.restapi.TopLevelResource;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
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;
|
||||
import com.google.gerrit.server.query.account.AccountQueryProcessor;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class QueryAccounts implements RestReadView<TopLevelResource> {
|
||||
private static final int MAX_SUGGEST_RESULTS = 100;
|
||||
private static final String MAX_SUFFIX = "\u9fa5";
|
||||
|
||||
private final AccountControl accountControl;
|
||||
private final AccountLoader.Factory accountLoaderFactory;
|
||||
private final AccountCache accountCache;
|
||||
private final AccountIndexCollection indexes;
|
||||
private final AccountQueryBuilder queryBuilder;
|
||||
private final AccountQueryProcessor queryProcessor;
|
||||
private final ReviewDb db;
|
||||
private final boolean suggestConfig;
|
||||
private final int suggestFrom;
|
||||
|
||||
private AccountLoader accountLoader;
|
||||
private boolean suggest;
|
||||
private int suggestLimit = 10;
|
||||
private String query;
|
||||
private Integer start;
|
||||
private EnumSet<ListAccountsOption> options;
|
||||
|
||||
@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) {
|
||||
suggestLimit = 10;
|
||||
} else if (n == 0) {
|
||||
suggestLimit = MAX_SUGGEST_RESULTS;
|
||||
} else {
|
||||
suggestLimit = Math.min(n, MAX_SUGGEST_RESULTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Option(name = "-o", usage = "Output options per account")
|
||||
public void addOption(ListAccountsOption o) {
|
||||
options.add(o);
|
||||
}
|
||||
|
||||
@Option(name = "-O", usage = "Output option flags, in hex")
|
||||
void setOptionFlagsHex(String hex) {
|
||||
options.addAll(ListAccountsOption.fromBits(Integer.parseInt(hex, 16)));
|
||||
}
|
||||
|
||||
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "match users")
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Option(name = "--start", aliases = {"-S"}, metaVar = "CNT",
|
||||
usage = "Number of accounts to skip")
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
@Inject
|
||||
QueryAccounts(AccountControl.Factory accountControlFactory,
|
||||
AccountLoader.Factory accountLoaderFactory,
|
||||
AccountCache accountCache,
|
||||
AccountIndexCollection indexes,
|
||||
AccountQueryBuilder queryBuilder,
|
||||
AccountQueryProcessor queryProcessor,
|
||||
ReviewDb db,
|
||||
@GerritServerConfig Config cfg) {
|
||||
this.accountControl = accountControlFactory.get();
|
||||
this.accountLoaderFactory = accountLoaderFactory;
|
||||
this.accountCache = accountCache;
|
||||
this.indexes = indexes;
|
||||
this.queryBuilder = queryBuilder;
|
||||
this.queryProcessor = queryProcessor;
|
||||
this.db = db;
|
||||
this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
|
||||
this.options = EnumSet.noneOf(ListAccountsOption.class);
|
||||
|
||||
if ("off".equalsIgnoreCase(cfg.getString("suggest", null, "accounts"))) {
|
||||
suggestConfig = false;
|
||||
} else {
|
||||
boolean suggest;
|
||||
try {
|
||||
AccountVisibility av =
|
||||
cfg.getEnum("suggest", null, "accounts", AccountVisibility.ALL);
|
||||
suggest = (av != AccountVisibility.NONE);
|
||||
} catch (IllegalArgumentException err) {
|
||||
suggest = cfg.getBoolean("suggest", null, "accounts", true);
|
||||
}
|
||||
this.suggestConfig = suggest;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountInfo> apply(TopLevelResource rsrc)
|
||||
throws OrmException, BadRequestException, MethodNotAllowedException {
|
||||
if (Strings.isNullOrEmpty(query)) {
|
||||
throw new BadRequestException("missing query field");
|
||||
}
|
||||
|
||||
if (suggest && (!suggestConfig || query.length() < suggestFrom)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
accountLoader = accountLoaderFactory
|
||||
.create(suggest || options.contains(ListAccountsOption.DETAILS));
|
||||
|
||||
AccountIndex searchIndex = indexes.getSearchIndex();
|
||||
if (searchIndex != null) {
|
||||
return queryFromIndex();
|
||||
}
|
||||
|
||||
if (!suggest) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
if (start != null) {
|
||||
throw new MethodNotAllowedException("option start not allowed");
|
||||
}
|
||||
return queryFromDb();
|
||||
}
|
||||
|
||||
public List<AccountInfo> queryFromIndex()
|
||||
throws BadRequestException, MethodNotAllowedException, OrmException {
|
||||
if (queryProcessor.isDisabled()) {
|
||||
throw new MethodNotAllowedException("query disabled");
|
||||
}
|
||||
|
||||
if (start != null) {
|
||||
queryProcessor.setStart(start);
|
||||
}
|
||||
|
||||
Map<Account.Id, AccountInfo> matches = new LinkedHashMap<>();
|
||||
try {
|
||||
Predicate<AccountState> queryPred;
|
||||
if (suggest) {
|
||||
queryPred = queryBuilder.defaultField(query);
|
||||
queryProcessor.setLimit(suggestLimit);
|
||||
} else {
|
||||
queryPred = queryBuilder.parse(query);
|
||||
}
|
||||
QueryResult<AccountState> result = queryProcessor.query(queryPred);
|
||||
for (AccountState accountState : result.entities()) {
|
||||
Account.Id id = accountState.getAccount().getId();
|
||||
matches.put(id, accountLoader.get(id));
|
||||
}
|
||||
|
||||
accountLoader.fill();
|
||||
|
||||
List<AccountInfo> sorted =
|
||||
AccountInfoComparator.ORDER_NULLS_LAST.sortedCopy(matches.values());
|
||||
if (!sorted.isEmpty() && result.more()) {
|
||||
sorted.get(sorted.size() - 1)._moreAccounts = true;
|
||||
}
|
||||
return sorted;
|
||||
} catch (QueryParseException e) {
|
||||
if (suggest) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
throw new BadRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public List<AccountInfo> queryFromDb() throws OrmException {
|
||||
String a = query;
|
||||
String b = a + MAX_SUFFIX;
|
||||
|
||||
Map<Account.Id, AccountInfo> matches = new LinkedHashMap<>();
|
||||
Map<Account.Id, String> queryEmail = new HashMap<>();
|
||||
|
||||
for (Account p : db.accounts().suggestByFullName(a, b, suggestLimit)) {
|
||||
addSuggestion(matches, p);
|
||||
}
|
||||
if (matches.size() < suggestLimit) {
|
||||
for (Account p : db.accounts()
|
||||
.suggestByPreferredEmail(a, b, suggestLimit - matches.size())) {
|
||||
addSuggestion(matches, p);
|
||||
}
|
||||
}
|
||||
if (matches.size() < suggestLimit) {
|
||||
for (AccountExternalId e : db.accountExternalIds()
|
||||
.suggestByEmailAddress(a, b, suggestLimit - matches.size())) {
|
||||
if (addSuggestion(matches, e.getAccountId())) {
|
||||
queryEmail.put(e.getAccountId(), e.getEmailAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accountLoader.fill();
|
||||
for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
|
||||
AccountInfo info = matches.get(p.getKey());
|
||||
if (info != null) {
|
||||
info.email = p.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return AccountInfoComparator.ORDER_NULLS_LAST.sortedCopy(matches.values());
|
||||
}
|
||||
|
||||
private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account a) {
|
||||
if (!a.isActive()) {
|
||||
return false;
|
||||
}
|
||||
Account.Id id = a.getId();
|
||||
if (!map.containsKey(id) && accountControl.canSee(id)) {
|
||||
map.put(id, accountLoader.get(id));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account.Id id) {
|
||||
Account a = accountCache.get(id).getAccount();
|
||||
return addSuggestion(map, a);
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Copyright (C) 2014 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.account;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
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.reviewdb.client.Account;
|
||||
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.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SuggestAccounts implements RestReadView<TopLevelResource> {
|
||||
private static final int MAX_RESULTS = 100;
|
||||
private static final String MAX_SUFFIX = "\u9fa5";
|
||||
|
||||
private final AccountControl accountControl;
|
||||
private final AccountLoader accountLoader;
|
||||
private final AccountCache accountCache;
|
||||
private final ReviewDb db;
|
||||
private final boolean suggest;
|
||||
private final int suggestFrom;
|
||||
|
||||
private int limit = 10;
|
||||
private String query;
|
||||
|
||||
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of users to return")
|
||||
public void setLimit(int n) {
|
||||
if (n < 0) {
|
||||
limit = 10;
|
||||
} else if (n == 0) {
|
||||
limit = MAX_RESULTS;
|
||||
} else {
|
||||
limit = Math.min(n, MAX_RESULTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "match users")
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Inject
|
||||
SuggestAccounts(AccountControl.Factory accountControlFactory,
|
||||
AccountLoader.Factory accountLoaderFactory,
|
||||
AccountCache accountCache,
|
||||
ReviewDb db,
|
||||
@GerritServerConfig Config cfg) {
|
||||
accountControl = accountControlFactory.get();
|
||||
accountLoader = accountLoaderFactory.create(true);
|
||||
this.accountCache = accountCache;
|
||||
this.db = db;
|
||||
this.suggestFrom = cfg.getInt("suggest", null, "from", 0);
|
||||
|
||||
if ("off".equalsIgnoreCase(cfg.getString("suggest", null, "accounts"))) {
|
||||
suggest = false;
|
||||
} else {
|
||||
boolean suggest;
|
||||
try {
|
||||
AccountVisibility av =
|
||||
cfg.getEnum("suggest", null, "accounts", AccountVisibility.ALL);
|
||||
suggest = (av != AccountVisibility.NONE);
|
||||
} catch (IllegalArgumentException err) {
|
||||
suggest = cfg.getBoolean("suggest", null, "accounts", true);
|
||||
}
|
||||
this.suggest = suggest;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccountInfo> apply(TopLevelResource rsrc)
|
||||
throws OrmException, BadRequestException {
|
||||
if (Strings.isNullOrEmpty(query)) {
|
||||
throw new BadRequestException("missing query field");
|
||||
}
|
||||
|
||||
if (!suggest || query.length() < suggestFrom) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String a = query;
|
||||
String b = a + MAX_SUFFIX;
|
||||
|
||||
Map<Account.Id, AccountInfo> matches = new LinkedHashMap<>();
|
||||
Map<Account.Id, String> queryEmail = new HashMap<>();
|
||||
|
||||
for (Account p : db.accounts().suggestByFullName(a, b, limit)) {
|
||||
addSuggestion(matches, p);
|
||||
}
|
||||
if (matches.size() < limit) {
|
||||
for (Account p : db.accounts()
|
||||
.suggestByPreferredEmail(a, b, limit - matches.size())) {
|
||||
addSuggestion(matches, p);
|
||||
}
|
||||
}
|
||||
if (matches.size() < limit) {
|
||||
for (AccountExternalId e : db.accountExternalIds()
|
||||
.suggestByEmailAddress(a, b, limit - matches.size())) {
|
||||
if (addSuggestion(matches, e.getAccountId())) {
|
||||
queryEmail.put(e.getAccountId(), e.getEmailAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accountLoader.fill();
|
||||
for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
|
||||
AccountInfo info = matches.get(p.getKey());
|
||||
if (info != null) {
|
||||
info.email = p.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return AccountInfoComparator.ORDER_NULLS_LAST.sortedCopy(matches.values());
|
||||
}
|
||||
|
||||
private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account a) {
|
||||
if (!a.isActive()) {
|
||||
return false;
|
||||
}
|
||||
Account.Id id = a.getId();
|
||||
if (!map.containsKey(id) && accountControl.canSee(id)) {
|
||||
map.put(id, accountLoader.get(id));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean addSuggestion(Map<Account.Id, AccountInfo> map, Account.Id id) {
|
||||
Account a = accountCache.get(id).getAccount();
|
||||
return addSuggestion(map, a);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package com.google.gerrit.server.api.accounts;
|
||||
|
||||
import com.google.gerrit.extensions.api.accounts.AccountApi;
|
||||
import com.google.gerrit.extensions.api.accounts.Accounts;
|
||||
import com.google.gerrit.extensions.client.ListAccountsOption;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.IdString;
|
||||
@@ -24,7 +25,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 +38,17 @@ public class AccountsImpl implements Accounts {
|
||||
private final AccountsCollection accounts;
|
||||
private final AccountApiImpl.Factory api;
|
||||
private final Provider<CurrentUser> self;
|
||||
private final Provider<SuggestAccounts> suggestAccountsProvider;
|
||||
private final Provider<QueryAccounts> queryAccountsProvider;
|
||||
|
||||
@Inject
|
||||
AccountsImpl(AccountsCollection accounts,
|
||||
AccountApiImpl.Factory api,
|
||||
Provider<CurrentUser> self,
|
||||
Provider<SuggestAccounts> suggestAccountsProvider) {
|
||||
Provider<QueryAccounts> queryAccountsProvider) {
|
||||
this.accounts = accounts;
|
||||
this.api = api;
|
||||
this.self = self;
|
||||
this.suggestAccountsProvider = suggestAccountsProvider;
|
||||
this.queryAccountsProvider = queryAccountsProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,10 +93,42 @@ public class AccountsImpl implements Accounts {
|
||||
private List<AccountInfo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryRequest query() throws RestApiException {
|
||||
return new QueryRequest() {
|
||||
@Override
|
||||
public List<AccountInfo> get() throws RestApiException {
|
||||
return AccountsImpl.this.query(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryRequest query(String query) throws RestApiException {
|
||||
return query().withQuery(query);
|
||||
}
|
||||
|
||||
private List<AccountInfo> query(QueryRequest r)
|
||||
throws RestApiException {
|
||||
try {
|
||||
QueryAccounts myQueryAccounts = queryAccountsProvider.get();
|
||||
myQueryAccounts.setQuery(r.getQuery());
|
||||
myQueryAccounts.setLimit(r.getLimit());
|
||||
myQueryAccounts.setStart(r.getStart());
|
||||
for (ListAccountsOption option : r.getOptions()) {
|
||||
myQueryAccounts.addOption(option);
|
||||
}
|
||||
return myQueryAccounts.apply(TopLevelResource.INSTANCE);
|
||||
} catch (OrmException e) {
|
||||
throw new RestApiException("Cannot retrieve suggested accounts", e);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AccountField {
|
||||
// Additional values not currently added by getPersonParts.
|
||||
// TODO(dborowitz): Move to getPersonParts and remove this hack.
|
||||
if (fullName != null) {
|
||||
parts.add(fullName);
|
||||
parts.add(fullName.toLowerCase());
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ public class AccountPredicates {
|
||||
|
||||
static Predicate<AccountState> email(String email) {
|
||||
return new AccountPredicate(AccountField.EMAIL,
|
||||
AccountQueryBuilder.FIELD_EMAIL, email);
|
||||
AccountQueryBuilder.FIELD_EMAIL, email.toLowerCase());
|
||||
}
|
||||
|
||||
static Predicate<AccountState> equalsName(String name) {
|
||||
return new AccountPredicate(AccountField.NAME_PART,
|
||||
AccountQueryBuilder.FIELD_NAME, name);
|
||||
AccountQueryBuilder.FIELD_NAME, name.toLowerCase());
|
||||
}
|
||||
|
||||
public static Predicate<AccountState> isActive() {
|
||||
@@ -53,7 +53,7 @@ public class AccountPredicates {
|
||||
|
||||
static Predicate<AccountState> username(String username) {
|
||||
return new AccountPredicate(AccountField.USERNAME,
|
||||
AccountQueryBuilder.FIELD_USERNAME, username);
|
||||
AccountQueryBuilder.FIELD_USERNAME, username.toLowerCase());
|
||||
}
|
||||
|
||||
static class AccountPredicate extends IndexPredicate<AccountState> {
|
||||
|
||||
@@ -123,7 +123,7 @@ public class AccountQueryBuilder extends QueryBuilder<AccountState> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate<AccountState> defaultField(String query)
|
||||
public Predicate<AccountState> defaultField(String query)
|
||||
throws QueryParseException {
|
||||
if ("self".equalsIgnoreCase(query)) {
|
||||
return AccountPredicates.id(self());
|
||||
|
||||
@@ -57,6 +57,6 @@ public class AccountQueryProcessor extends QueryProcessor<AccountState> {
|
||||
protected Predicate<AccountState> enforceVisibility(
|
||||
Predicate<AccountState> pred) {
|
||||
return new AndSource<>(pred,
|
||||
new AccountIsVisibleToPredicate(accountControlFactory.get()));
|
||||
new AccountIsVisibleToPredicate(accountControlFactory.get()), start);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
// Copyright (C) 2016 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.query.account;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gerrit.extensions.api.GerritApi;
|
||||
import com.google.gerrit.extensions.api.accounts.Accounts.QueryRequest;
|
||||
import com.google.gerrit.extensions.client.ListAccountsOption;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||
import com.google.gerrit.lifecycle.LifecycleManager;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.account.AuthRequest;
|
||||
import com.google.gerrit.server.query.change.InternalChangeQuery;
|
||||
import com.google.gerrit.server.schema.SchemaCreator;
|
||||
import com.google.gerrit.server.util.RequestContext;
|
||||
import com.google.gerrit.server.util.ThreadLocalRequestContext;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
import com.google.gerrit.testutil.GerritServerTests;
|
||||
import com.google.gerrit.testutil.InMemoryDatabase;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.util.Providers;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@Ignore
|
||||
public abstract class AbstractQueryAccountsTest extends GerritServerTests {
|
||||
@ConfigSuite.Default
|
||||
public static Config defaultConfig() {
|
||||
Config cfg = new Config();
|
||||
cfg.setInt("index", null, "maxPages", 10);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@Rule
|
||||
public final TestName testName = new TestName();
|
||||
|
||||
@Inject
|
||||
protected AccountCache accountCache;
|
||||
|
||||
@Inject
|
||||
protected AccountManager accountManager;
|
||||
|
||||
@Inject
|
||||
protected GerritApi gApi;
|
||||
|
||||
@Inject
|
||||
protected IdentifiedUser.GenericFactory userFactory;
|
||||
|
||||
@Inject
|
||||
protected InMemoryDatabase schemaFactory;
|
||||
|
||||
@Inject
|
||||
protected InternalChangeQuery internalChangeQuery;
|
||||
|
||||
@Inject
|
||||
protected SchemaCreator schemaCreator;
|
||||
|
||||
@Inject
|
||||
protected ThreadLocalRequestContext requestContext;
|
||||
|
||||
protected LifecycleManager lifecycle;
|
||||
protected ReviewDb db;
|
||||
protected AccountInfo currentUserInfo;
|
||||
protected CurrentUser user;
|
||||
|
||||
protected abstract Injector createInjector();
|
||||
|
||||
@Before
|
||||
public void setUpInjector() throws Exception {
|
||||
lifecycle = new LifecycleManager();
|
||||
Injector injector = createInjector();
|
||||
lifecycle.add(injector);
|
||||
injector.injectMembers(this);
|
||||
lifecycle.start();
|
||||
|
||||
db = schemaFactory.open();
|
||||
schemaCreator.create(db);
|
||||
|
||||
Account.Id userId = createAccount("user", "User", "user@example.com", true);
|
||||
user = userFactory.create(userId);
|
||||
requestContext.setContext(newRequestContext(userId));
|
||||
currentUserInfo = gApi.accounts().id(userId.get()).get();
|
||||
}
|
||||
|
||||
protected RequestContext newRequestContext(Account.Id requestUserId) {
|
||||
final CurrentUser requestUser =
|
||||
userFactory.create(requestUserId);
|
||||
return new RequestContext() {
|
||||
@Override
|
||||
public CurrentUser getUser() {
|
||||
return requestUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider<ReviewDb> getReviewDbProvider() {
|
||||
return Providers.of(db);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDownInjector() {
|
||||
if (lifecycle != null) {
|
||||
lifecycle.stop();
|
||||
}
|
||||
requestContext.setContext(null);
|
||||
if (db != null) {
|
||||
db.close();
|
||||
}
|
||||
InMemoryDatabase.drop(schemaFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byId() throws Exception {
|
||||
AccountInfo user = newAccount("user");
|
||||
|
||||
assertQuery("9999999");
|
||||
assertQuery(currentUserInfo._accountId, currentUserInfo);
|
||||
assertQuery(user._accountId, user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bySelf() throws Exception {
|
||||
assertQuery("self", currentUserInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byEmail() throws Exception {
|
||||
AccountInfo user1 = newAccountWithEmail("user1", name("user1@example.com"));
|
||||
|
||||
String domain = name("test.com");
|
||||
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
|
||||
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
|
||||
|
||||
String prefix = name("prefix");
|
||||
AccountInfo user4 =
|
||||
newAccountWithEmail("user4", prefix + "user4@example.com");
|
||||
|
||||
AccountInfo user5 =
|
||||
newAccountWithEmail("user5", name("user5MixedCase@example.com"));
|
||||
|
||||
assertQuery("notexisting@test.com");
|
||||
|
||||
assertQuery(currentUserInfo.email, currentUserInfo);
|
||||
assertQuery("email:" + currentUserInfo.email, currentUserInfo);
|
||||
|
||||
assertQuery(user1.email, user1);
|
||||
assertQuery("email:" + user1.email, user1);
|
||||
|
||||
assertQuery(domain, user2, user3);
|
||||
|
||||
assertQuery("email:" + prefix, user4);
|
||||
|
||||
assertQuery(user5.email, user5);
|
||||
assertQuery("email:" + user5.email, user5);
|
||||
assertQuery("email:" + user5.email.toUpperCase(), user5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byUsername() throws Exception {
|
||||
AccountInfo user1 = newAccount("myuser");
|
||||
|
||||
assertQuery("notexisting");
|
||||
assertQuery("Not Existing");
|
||||
|
||||
assertQuery(user1.username, user1);
|
||||
assertQuery("username:" + user1.username, user1);
|
||||
assertQuery("username:" + user1.username.toUpperCase(), user1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isActive() throws Exception {
|
||||
String domain = name("test.com");
|
||||
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
|
||||
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
|
||||
AccountInfo user3 = newAccount("user3", "user3@" + domain, false);
|
||||
AccountInfo user4 = newAccount("user4", "user4@" + domain, false);
|
||||
|
||||
// by default only active accounts are returned
|
||||
assertQuery(domain, user1, user2);
|
||||
assertQuery("name:" + domain, user1, user2);
|
||||
|
||||
assertQuery("is:active name:" + domain, user1, user2);
|
||||
|
||||
assertQuery("is:inactive name:" + domain, user3, user4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void byName() throws Exception {
|
||||
AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
|
||||
AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
|
||||
|
||||
assertQuery("notexisting");
|
||||
assertQuery("Not Existing");
|
||||
|
||||
assertQuery(quote(user1.name), user1);
|
||||
assertQuery("name:" + quote(user1.name), user1);
|
||||
assertQuery("John", user1);
|
||||
assertQuery("john", user1);
|
||||
assertQuery("Doe", user1);
|
||||
assertQuery("doe", user1);
|
||||
assertQuery("DOE", user1);
|
||||
assertQuery("name:John", user1);
|
||||
assertQuery("name:john", user1);
|
||||
assertQuery("name:Doe", user1);
|
||||
assertQuery("name:doe", user1);
|
||||
assertQuery("name:DOE", user1);
|
||||
|
||||
assertQuery(quote(user2.name), user2);
|
||||
assertQuery("name:" + quote(user2.name), user2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withLimit() throws Exception {
|
||||
String domain = name("test.com");
|
||||
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
|
||||
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
|
||||
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
|
||||
|
||||
List<AccountInfo> result = assertQuery(domain, user1, user2, user3);
|
||||
assertThat(result.get(result.size() - 1)._moreAccounts).isNull();
|
||||
|
||||
result = assertQuery(newQuery(domain).withLimit(2), user1, user2);
|
||||
assertThat(result.get(result.size() - 1)._moreAccounts).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withStart() throws Exception {
|
||||
String domain = name("test.com");
|
||||
AccountInfo user1 = newAccountWithEmail("user1", "user1@" + domain);
|
||||
AccountInfo user2 = newAccountWithEmail("user2", "user2@" + domain);
|
||||
AccountInfo user3 = newAccountWithEmail("user3", "user3@" + domain);
|
||||
|
||||
assertQuery(domain, user1, user2, user3);
|
||||
assertQuery(newQuery(domain).withStart(1), user2, user3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDetails() throws Exception {
|
||||
AccountInfo user1 =
|
||||
newAccount("myuser", "My User", "my.user@example.com", true);
|
||||
|
||||
List<AccountInfo> result = assertQuery(user1.username, user1);
|
||||
AccountInfo ai = result.get(0);
|
||||
assertThat(ai._accountId).isEqualTo(user1._accountId);
|
||||
assertThat(ai.name).isNull();
|
||||
assertThat(ai.username).isNull();
|
||||
assertThat(ai.email).isNull();
|
||||
assertThat(ai.avatars).isNull();
|
||||
|
||||
result = assertQuery(
|
||||
newQuery(user1.username).withOption(ListAccountsOption.DETAILS), user1);
|
||||
ai = result.get(0);
|
||||
assertThat(ai._accountId).isEqualTo(user1._accountId);
|
||||
assertThat(ai.name).isEqualTo(user1.name);
|
||||
assertThat(ai.username).isEqualTo(user1.username);
|
||||
assertThat(ai.email).isEqualTo(user1.email);
|
||||
assertThat(ai.avatars).isNull();
|
||||
}
|
||||
|
||||
protected AccountInfo newAccount(String username) throws Exception {
|
||||
return newAccountWithEmail(username, null);
|
||||
}
|
||||
|
||||
protected AccountInfo newAccountWithEmail(String username, String email)
|
||||
throws Exception {
|
||||
return newAccount(username, email, true);
|
||||
}
|
||||
|
||||
protected AccountInfo newAccountWithFullName(String username, String fullName)
|
||||
throws Exception {
|
||||
return newAccount(username, fullName, null, true);
|
||||
}
|
||||
|
||||
protected AccountInfo newAccount(String username, String email,
|
||||
boolean active) throws Exception {
|
||||
return newAccount(username, null, email, active);
|
||||
}
|
||||
|
||||
protected AccountInfo newAccount(String username, String fullName,
|
||||
String email, boolean active) throws Exception {
|
||||
String uniqueName = name(username);
|
||||
|
||||
try {
|
||||
gApi.accounts().id(uniqueName).get();
|
||||
fail("user " + uniqueName + " already exists");
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// expected: user does not exist yet
|
||||
}
|
||||
|
||||
Account.Id id = createAccount(uniqueName, fullName, email, active);
|
||||
return gApi.accounts().id(id.get()).get();
|
||||
}
|
||||
|
||||
protected String quote(String s) {
|
||||
return "\"" + s + "\"";
|
||||
}
|
||||
|
||||
protected String name(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
String suffix = testName.getMethodName().toLowerCase();
|
||||
if (name.contains("@")) {
|
||||
return name + "." + suffix;
|
||||
}
|
||||
return name + "_" + suffix;
|
||||
}
|
||||
|
||||
private Account.Id createAccount(String username, String fullName,
|
||||
String email, boolean active) throws Exception {
|
||||
Account.Id id =
|
||||
accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
|
||||
if (email != null) {
|
||||
accountManager.link(id, AuthRequest.forEmail(email));
|
||||
}
|
||||
Account a = db.accounts().get(id);
|
||||
a.setFullName(fullName);
|
||||
a.setPreferredEmail(email);
|
||||
a.setActive(active);
|
||||
db.accounts().update(ImmutableList.of(a));
|
||||
accountCache.evict(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
protected QueryRequest newQuery(Object query) throws RestApiException {
|
||||
return gApi.accounts().query(query.toString());
|
||||
}
|
||||
|
||||
protected List<AccountInfo> assertQuery(Object query, AccountInfo... accounts)
|
||||
throws Exception {
|
||||
return assertQuery(newQuery(query), accounts);
|
||||
}
|
||||
|
||||
protected List<AccountInfo> assertQuery(QueryRequest query, AccountInfo... accounts)
|
||||
throws Exception {
|
||||
List<AccountInfo> result = query.get();
|
||||
Iterable<Integer> ids = ids(result);
|
||||
assertThat(ids).named(format(query, result, accounts))
|
||||
.containsExactlyElementsIn(ids(accounts)).inOrder();
|
||||
return result;
|
||||
}
|
||||
|
||||
private String format(QueryRequest query, Iterable<AccountInfo> actualIds,
|
||||
AccountInfo... expectedAccounts) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("query '").append(query.getQuery())
|
||||
.append("' with expected accounts ");
|
||||
b.append(format(Arrays.asList(expectedAccounts)));
|
||||
b.append(" and result ");
|
||||
b.append(format(actualIds));
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private String format(Iterable<AccountInfo> accounts) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("[");
|
||||
Iterator<AccountInfo> it = accounts.iterator();
|
||||
while (it.hasNext()) {
|
||||
AccountInfo a = it.next();
|
||||
b.append("{").append(a._accountId).append(", ").append("name=")
|
||||
.append(a.name).append(", ").append("email=").append(a.email)
|
||||
.append(", ").append("username=").append(a.username).append("}");
|
||||
if (it.hasNext()) {
|
||||
b.append(", ");
|
||||
}
|
||||
}
|
||||
b.append("]");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
protected static Iterable<Integer> ids(AccountInfo... accounts) {
|
||||
return FluentIterable.from(Arrays.asList(accounts)).transform(
|
||||
new Function<AccountInfo, Integer>() {
|
||||
@Override
|
||||
public Integer apply(AccountInfo in) {
|
||||
return in._accountId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected static Iterable<Integer> ids(Iterable<AccountInfo> accounts) {
|
||||
return FluentIterable.from(accounts).transform(
|
||||
new Function<AccountInfo, Integer>() {
|
||||
@Override
|
||||
public Integer apply(AccountInfo in) {
|
||||
return in._accountId;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2016 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.query.account;
|
||||
|
||||
import com.google.gerrit.testutil.InMemoryModule;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
|
||||
public class LuceneQueryAccountsTest extends AbstractQueryAccountsTest {
|
||||
@Override
|
||||
protected Injector createInjector() {
|
||||
Config luceneConfig = new Config(config);
|
||||
InMemoryModule.setDefaults(luceneConfig);
|
||||
return Guice.createInjector(
|
||||
new InMemoryModule(luceneConfig, notesMigration));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user