Build a Recommender for Reviewer Suggestion

Until now, reviewer suggestion was purely based on a search. I've built
a small recommender to improve the suggestions based on past
contributions by the individual reviewers and added an extension point
so that people can customize this feature.

The built-in recommender makes a default suggestion of reviewers before
the user types a query. These are based on people that have reviewed the
last contributions that a user made.

If the user starts typing in the box, we generate a list of candidates
using the account index and feed it into a small recommender. The
recommender ranks the list by looking at recent contributions of the
candidates made in the same project. Contributions include reviews,
owned-changes and comments at different weights.

Change-Id: I5aca23ddd2442146fd26bdc12e7c18da85de7ac1
This commit is contained in:
Patrick Hiesel
2016-05-03 18:15:08 +02:00
parent a4b637d578
commit 87880b0543
17 changed files with 723 additions and 143 deletions

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.client.ui;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.SuggestOracle;
/**
@@ -31,6 +32,10 @@ public class RemoteSuggestOracle extends SuggestOracle {
private final SuggestOracle oracle;
private Query query;
private String last;
private Timer requestRetentionTimer;
private boolean cancelOutstandingRequest;
private boolean serveSuggestions;
public RemoteSuggestOracle(SuggestOracle src) {
oracle = src;
@@ -42,13 +47,33 @@ public class RemoteSuggestOracle extends SuggestOracle {
@Override
public void requestSuggestions(Request req, Callback cb) {
Query q = new Query(req, cb);
if (query == null) {
query = q;
q.start();
} else {
query = q;
if (!serveSuggestions){
return;
}
// Use a timer for key stroke retention, such that we don't query the
// backend for each and every keystroke we receive.
if (requestRetentionTimer != null) {
requestRetentionTimer.cancel();
}
requestRetentionTimer = new Timer() {
@Override
public void run() {
Query q = new Query(req, cb);
if (query == null) {
query = q;
q.start();
} else {
query = q;
}
}
};
requestRetentionTimer.schedule(200);
}
@Override
public void requestDefaultSuggestions(Request req, Callback cb) {
requestSuggestions(req, cb);
}
@Override
@@ -56,6 +81,19 @@ public class RemoteSuggestOracle extends SuggestOracle {
return oracle.isDisplayStringHTML();
}
public void cancelOutstandingRequest() {
if (requestRetentionTimer != null) {
requestRetentionTimer.cancel();
}
if (query != null) {
cancelOutstandingRequest = true;
}
}
public void setServeSuggestions(boolean serveSuggestions) {
this.serveSuggestions = serveSuggestions;
}
private class Query implements Callback {
final Request request;
final Callback callback;
@@ -71,7 +109,11 @@ public class RemoteSuggestOracle extends SuggestOracle {
@Override
public void onSuggestionsReady(Request req, Response res) {
if (query == this) {
if (cancelOutstandingRequest || !serveSuggestions) {
// If cancelOutstandingRequest() was called, we ignore this response
cancelOutstandingRequest = false;
query = null;
} else if (query == this) {
// No new request was started while this query was running.
// Propose this request's response as the suggestions.
query = null;