Full text search in memory for review suggestions

This change also makes it possible to configure maximum
displayed reviewers.

On some Gerrit instances the full name is formatted like:
<given name> <surname>
and email like:
<given name>.<surname>@...
This would make it impossible to get reviewer suggestions from
surnames.
Since gwtorm doesn't support sql LIKE there is no straight forward
way of filtering on substring in the DB. Hence this in memory approach.

For performance reasons this implementation differs from the default
implementation in that it does not look at the email_address in
account_external_ids but only at the preferred_email of accounts.
The default implementation does only look for 10 matches and afterwards
filters out the acconts that are not allowed to view the change.

Configuration:
suggest.maxSuggestedReviewers
Maximum number of suggested reviewers (default 10).

suggest.fullTextSearch
Enable full text search (default "false").

suggest.fullTextSearchMaxMatches
Maximum number of matches to be checked for accessability when using
full text search (default 100).

Change-Id: Ia4c3a15263783bc144e66a05854c3915392095b5
This commit is contained in:
Sven Selberg
2014-08-13 11:20:11 +02:00
committed by Edwin Kempin
parent 2501e548e2
commit 42d9d297d5
4 changed files with 178 additions and 5 deletions

View File

@@ -57,7 +57,8 @@ import java.util.Set;
public class SuggestReviewers implements RestReadView<ChangeResource> {
private static final String MAX_SUFFIX = "\u9fa5";
private static final int MAX = 10;
private static final int DEFAULT_MAX_SUGGESTED = 10;
private static final int DEFAULT_MAX_MATCHES = 100;
private final AccountInfo.Loader.Factory accountLoaderFactory;
private final AccountControl.Factory accountControlFactory;
@@ -72,11 +73,17 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
private final int maxAllowed;
private int limit;
private String query;
private boolean useFullTextSearch;
private final int fullTextMaxMatches;
private final int maxSuggestedReviewers;
private final ReviewerSuggestionCache reviewerSuggestionCache;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT",
usage = "maximum number of reviewers to list")
public void setLimit(int l) {
this.limit = l <= 0 ? MAX : Math.min(l, MAX);
this.limit =
l <= 0 ? maxSuggestedReviewers : Math.min(l,
maxSuggestedReviewers);
}
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY",
@@ -95,7 +102,8 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
Provider<CurrentUser> currentUser,
Provider<ReviewDb> dbProvider,
@GerritServerConfig Config cfg,
GroupBackend groupBackend) {
GroupBackend groupBackend,
ReviewerSuggestionCache reviewerSuggestionCache) {
this.accountLoaderFactory = accountLoaderFactory;
this.accountControlFactory = accountControlFactory;
this.accountCache = accountCache;
@@ -104,12 +112,18 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
this.identifiedUserFactory = identifiedUserFactory;
this.currentUser = currentUser;
this.groupBackend = groupBackend;
this.reviewerSuggestionCache = reviewerSuggestionCache;
this.maxSuggestedReviewers =
cfg.getInt("suggest", "maxSuggestedReviewers", DEFAULT_MAX_SUGGESTED);
this.fullTextMaxMatches =
cfg.getInt("suggest", "fullTextSearchMaxMatches",
DEFAULT_MAX_MATCHES);
String suggest = cfg.getString("suggest", null, "accounts");
if ("OFF".equalsIgnoreCase(suggest)
|| "false".equalsIgnoreCase(suggest)) {
this.suggestAccounts = false;
} else {
this.useFullTextSearch = cfg.getBoolean("suggest", "fullTextSearch", false);
this.suggestAccounts = (av != AccountVisibility.NONE);
}
@@ -134,7 +148,12 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
}
VisibilityControl visibilityControl = getVisibility(rsrc);
List<AccountInfo> suggestedAccounts = suggestAccount(visibilityControl);
List<AccountInfo> suggestedAccounts;
if (useFullTextSearch) {
suggestedAccounts = suggestAccountFullTextSearch(visibilityControl);
} else {
suggestedAccounts = suggestAccount(visibilityControl);
}
accountLoaderFactory.create(true).fill(suggestedAccounts);
List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
@@ -220,6 +239,42 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
return Lists.newArrayList(r.values());
}
private List<AccountInfo> suggestAccountFullTextSearch(
VisibilityControl visibilityControl) throws OrmException {
String str = query.toLowerCase();
LinkedHashMap<Account.Id, AccountInfo> accountMap = Maps.newLinkedHashMap();
List<Account> fullNameMatches = Lists.newArrayListWithCapacity(fullTextMaxMatches);
List<Account> emailMatches = Lists.newArrayListWithCapacity(fullTextMaxMatches);
for (Account a : reviewerSuggestionCache.get()) {
if (a.getFullName() != null
&& a.getFullName().toLowerCase().contains(str)) {
fullNameMatches.add(a);
} else if (a.getPreferredEmail() != null
&& emailMatches.size() < fullTextMaxMatches
&& a.getPreferredEmail().toLowerCase().contains(str)) {
emailMatches.add(a);
}
if (fullNameMatches.size() >= fullTextMaxMatches) {
break;
}
}
for (Account a : fullNameMatches) {
addSuggestion(accountMap, a, new AccountInfo(a.getId()), visibilityControl);
if (accountMap.size() >= limit) {
break;
}
}
if (accountMap.size() < limit) {
for (Account a : emailMatches) {
addSuggestion(accountMap, a, new AccountInfo(a.getId()), visibilityControl);
if (accountMap.size() >= limit) {
break;
}
}
}
return Lists.newArrayList(accountMap.values());
}
private void addSuggestion(Map<Account.Id, AccountInfo> map, Account account,
AccountInfo info, VisibilityControl visibilityControl)
throws OrmException {