Rewrite remote oracle wrapper to reduce requests

Gerrit was sending a suggest request roughly for every key typed.  On
a slow connection (or a slow server) this behavior caused a lot of
suggestion requests whose results were immediately useless when the
next key was processed.

Wrap the reviewer suggestion with RemoteSuggestOracle, which allows at
most one remote query to execute at a time.  Intermediate queries are
discarded, reducing the number of remote calls made by the UI when the
user is typing faster than the network+server can process.

This somewhat improves suggestion experience for reviewers on a slow
site.  Typing "John D<enter>" is now more likely to complete to a
person named "John Doe" rather than "John Alberts", which was being
selected from a stale result based on the "John" suggestion list.

Change-Id: I9bfaa2fe5bd92bbbf38c086f7c909761854d718d
This commit is contained in:
Shawn Pearce
2014-12-29 12:45:36 -05:00
parent 7b4cd6f5ee
commit 59a19adffe
7 changed files with 102 additions and 74 deletions

View File

@@ -21,7 +21,7 @@ import com.google.gerrit.client.groups.GroupInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.client.ui.RemoteSuggestOracle;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gwt.event.dom.client.ClickEvent;
@@ -120,7 +120,7 @@ public class AccountGroupInfoScreen extends AccountGroupScreen {
ownerTxtBox = new NpTextBox();
ownerTxtBox.setVisibleLength(60);
final AccountGroupSuggestOracle accountGroupOracle = new AccountGroupSuggestOracle();
ownerTxt = new SuggestBox(new RPCSuggestOracle(
ownerTxt = new SuggestBox(new RemoteSuggestOracle(
accountGroupOracle), ownerTxtBox);
ownerTxt.setStyleName(Gerrit.RESOURCES.css().groupOwnerTextBox());
ownerPanel.add(ownerTxt);

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.client.ui.RemoteSuggestOracle;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.editor.client.LeafValueEditor;
@@ -53,7 +53,7 @@ public class GroupReferenceBox extends Composite implements
textBox = new NpTextBox();
oracle = new AccountGroupSuggestOracle();
suggestBox = new SuggestBox( //
new RPCSuggestOracle(oracle), //
new RemoteSuggestOracle(oracle), //
textBox, //
suggestions);
initWidget(suggestBox);

View File

@@ -25,9 +25,9 @@ import com.google.gerrit.client.ui.SuggestAfterTypingNCharsOracle;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.SuggestOracle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** REST API based suggestion Oracle for reviewers. */
@@ -35,17 +35,22 @@ public class ReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
private Change.Id changeId;
@Override
protected void _onRequestSuggestions(final Request req, final Callback callback) {
ChangeApi.suggestReviewers(changeId.get(), req.getQuery(),
req.getLimit()).get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
protected void _onRequestSuggestions(final Request req, final Callback cb) {
ChangeApi.suggestReviewers(changeId.get(), req.getQuery(), req.getLimit())
.get(new GerritCallback<JsArray<SuggestReviewerInfo>>() {
@Override
public void onSuccess(JsArray<SuggestReviewerInfo> result) {
final List<RestReviewerSuggestion> r =
new ArrayList<>(result.length());
for (final SuggestReviewerInfo reviewer : Natives.asList(result)) {
List<RestReviewerSuggestion> r = new ArrayList<>(result.length());
for (SuggestReviewerInfo reviewer : Natives.asList(result)) {
r.add(new RestReviewerSuggestion(reviewer));
}
callback.onSuggestionsReady(req, new Response(r));
cb.onSuggestionsReady(req, new Response(r));
}
@Override
public void onFailure(Throwable err) {
List<Suggestion> r = Collections.emptyList();
cb.onSuggestionsReady(req, new Response(r));
}
});
}
@@ -54,7 +59,7 @@ public class ReviewerSuggestOracle extends SuggestAfterTypingNCharsOracle {
this.changeId = changeId;
}
private static class RestReviewerSuggestion implements SuggestOracle.Suggestion {
private static class RestReviewerSuggestion implements Suggestion {
private final SuggestReviewerInfo reviewer;
RestReviewerSuggestion(final SuggestReviewerInfo reviewer) {

View File

@@ -28,6 +28,7 @@ import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.client.ui.RemoteSuggestOracle;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
@@ -82,7 +83,9 @@ public class Reviewers extends Composite {
Reviewers() {
reviewerSuggestOracle = new ReviewerSuggestOracle();
nameTxtBox = new HintTextBox();
suggestBox = new SuggestBox(reviewerSuggestOracle, nameTxtBox);
suggestBox = new SuggestBox(
new RemoteSuggestOracle(reviewerSuggestOracle),
nameTxtBox);
initWidget(uiBinder.createAndBindUi(this));
nameTxtBox.setVisibleLength(55);

View File

@@ -42,7 +42,7 @@ public class AddMemberBox extends Composite {
addPanel = new FlowPanel();
addMember = new Button(buttonLabel);
nameTxtBox = new HintTextBox();
nameTxt = new SuggestBox(new RPCSuggestOracle(
nameTxt = new SuggestBox(new RemoteSuggestOracle(
suggestOracle), nameTxtBox);
nameTxt.setStyleName(Gerrit.RESOURCES.css().addMemberTextBox());

View File

@@ -1,59 +0,0 @@
// Copyright (C) 2010 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.client.ui;
import com.google.gwt.user.client.ui.SuggestOracle;
/** This class will proxy SuggestOracle requests to another SuggestOracle
* while keeping track of the latest request. Any repsonse that belongs
* to a request which is not the latest request will be dropped to prevent
* invalid deliveries.
*/
public class RPCSuggestOracle extends SuggestOracle {
private SuggestOracle oracle;
private SuggestOracle.Request request;
private SuggestOracle.Callback callback;
private SuggestOracle.Callback myCallback = new SuggestOracle.Callback() {
@Override
public void onSuggestionsReady(SuggestOracle.Request req,
SuggestOracle.Response response) {
if (request == req) {
callback.onSuggestionsReady(req, response);
request = null;
callback = null;
}
}
};
public RPCSuggestOracle(SuggestOracle ora) {
oracle = ora;
}
@Override
public void requestSuggestions(SuggestOracle.Request req,
SuggestOracle.Callback cb) {
request = req;
callback = cb;
oracle.requestSuggestions(req, myCallback);
}
@Override
public boolean isDisplayStringHTML() {
return oracle.isDisplayStringHTML();
}
}

View File

@@ -0,0 +1,79 @@
// Copyright (C) 2010 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.client.ui;
import com.google.gwt.user.client.ui.SuggestOracle;
/**
* Delegates to a slow SuggestOracle, such as a remote server API.
* <p>
* A response is only supplied to the UI if no requests were made after the
* oracle begin that request.
* <p>
* When a request is made while the delegate is still processing a prior request
* all intermediate requests are discarded and the most recent request is
* queued. The pending request's response is discarded and the most recent
* request is started.
*/
public class RemoteSuggestOracle extends SuggestOracle {
private final SuggestOracle oracle;
private Query query;
public RemoteSuggestOracle(SuggestOracle src) {
oracle = src;
}
@Override
public void requestSuggestions(Request req, Callback cb) {
Query q = new Query(req, cb);
if (query == null) {
q.start();
}
query = q;
}
@Override
public boolean isDisplayStringHTML() {
return oracle.isDisplayStringHTML();
}
private class Query implements Callback {
final Request request;
final Callback callback;
Query(Request req, Callback cb) {
request = req;
callback = cb;
}
void start() {
oracle.requestSuggestions(request, this);
}
@Override
public void onSuggestionsReady(Request req, Response res) {
if (query == this) {
// No new request was started while this query was running.
// Propose this request's response as the suggestions.
query = null;
callback.onSuggestionsReady(req, res);
} else {
// Another query came in while this one was running. Skip
// this response and start the most recent query.
query.start();
}
}
}
}