Catch RPC exceptions and display them in a modal dialog

Currently we really can't recover automatically from any RPC error,
so instead of failing silently in compiled mode (due the compiler
stripping out GWT.log calls) we show a modal popup to the user to
let them know things didn't go according to plan.

In the case of a star toggle, we untoggle it, but pretty much all
of the other actions don't really have a reverse, so we use the
default error display logic in the dialog.

Future versions of gwtjsonrpc should support automatically retrying
certain types of requests, especially when we can identify the error
as a network connectivity problem.

Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2008-12-02 18:23:21 -08:00
parent d005073fd3
commit 44b72d261c
16 changed files with 255 additions and 39 deletions

View File

@@ -0,0 +1,81 @@
// Copyright 2008 Google Inc.
//
// 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;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtjsonrpc.client.RemoteJsonException;
/** A dialog box showing an error message, when bad things happen. */
public class ErrorDialog extends DialogBox {
private final FlowPanel body;
protected ErrorDialog() {
super(/* auto hide */true, /* modal */true);
setText(Gerrit.C.errorDialogTitle());
body = new FlowPanel();
final FlowPanel buttons = new FlowPanel();
buttons.setStyleName("gerrit-ErrorDialog-Buttons");
final Button closey = new Button();
closey.setText(Gerrit.C.errorDialogClose());
closey.addClickListener(new ClickListener() {
public void onClick(final Widget sender) {
hide();
}
});
buttons.add(closey);
final FlowPanel center = new FlowPanel();
center.setStyleName("gerrit-ErrorDialog");
center.add(body);
center.add(buttons);
add(center);
}
/** Create a dialog box to show a single message string. */
public ErrorDialog(final String message) {
this();
body.add(label(message, "gerrit-ErrorDialog-ErrorMessage"));
}
/** Create a dialog box to nicely format an exception. */
public ErrorDialog(final Throwable what) {
this();
String cn;
if (what instanceof RemoteJsonException) {
cn = com.google.gerrit.client.rpc.Util.C.errorRemoteJsonException();
} else {
cn = what.getClass().getName();
if (cn.startsWith("java.lang.")) {
cn = cn.substring("java.lang.".length());
}
}
body.add(label(cn, "gerrit-ErrorDialog-ErrorType"));
body.add(label(what.getMessage(), "gerrit-ErrorDialog-ErrorMessage"));
}
private static Label label(final String what, final String style) {
final Label r = new Label(what);
r.setStyleName(style);
return r;
}
}

View File

@@ -22,6 +22,9 @@ public interface GerritConstants extends Constants {
String menuSettings();
String signInDialogTitle();
String errorDialogTitle();
String errorDialogClose();
String notFoundTitle();
String notFoundBody();

View File

@@ -3,6 +3,9 @@ menuSignOut = Sign Out
menuSettings = Settings
signInDialogTitle = Code Review - Sign In
errorDialogTitle = Code Review - Unexpected Error
errorDialogClose = Close
notFoundTitle = Not Found
notFoundBody = The page you requested was not found.

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.client;
import com.google.gerrit.client.account.SignInResult;
import com.google.gerrit.client.account.SignInResult.Status;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
@@ -61,14 +62,10 @@ public class SignInDialog extends DialogBox {
signInCallback =
com.google.gerrit.client.account.Util.LOGIN_SVC
.signIn(new AsyncCallback<SignInResult>() {
.signIn(new GerritCallback<SignInResult>() {
public void onSuccess(final SignInResult result) {
onCallback(result);
}
public void onFailure(Throwable caught) {
GWT.log("Unexpected signIn failure", caught);
}
});
appCallback = callback;

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.client.account;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.NotSignedInException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.CookieAccess;
import com.google.gwtorm.client.OrmException;
@@ -32,7 +33,7 @@ public class AccountServiceImpl implements AccountService {
public void myAccount(final AsyncCallback<Account> callback) {
final int id = idFromCookie(callback);
if (id <= 0) {
callback.onSuccess(null);
callback.onFailure(new NotSignedInException());
return;
}

View File

@@ -15,9 +15,9 @@
package com.google.gerrit.client.account;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
public class AccountSettings extends AccountScreen {
public AccountSettings() {
@@ -27,11 +27,7 @@ public class AccountSettings extends AccountScreen {
@Override
public void onLoad() {
super.onLoad();
Util.ACCOUNT_SVC.myAccount(new AsyncCallback<Account>() {
public void onFailure(Throwable caught) {
GWT.log("myAccount failed", caught);
}
Util.ACCOUNT_SVC.myAccount(new GerritCallback<Account>() {
public void onSuccess(Account result) {
GWT.log("yay, i am " + result.getPreferredEmail(), null);
GWT.log("created on " + result.getRegisteredOn(), null);

View File

@@ -17,10 +17,9 @@ package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.AccountDashboardInfo;
import com.google.gerrit.client.data.AccountInfo;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.Screen;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.rpc.AsyncCallback;
public class AccountDashboardScreen extends Screen {
@@ -58,14 +57,10 @@ public class AccountDashboardScreen extends Screen {
super.onLoad();
table.setSavePointerId(History.getToken());
Util.LIST_SVC.forAccount(ownerId,
new AsyncCallback<AccountDashboardInfo>() {
new GerritCallback<AccountDashboardInfo>() {
public void onSuccess(final AccountDashboardInfo r) {
display(r);
}
public void onFailure(final Throwable caught) {
GWT.log("Fail", caught);
}
});
}

View File

@@ -11,6 +11,7 @@ import com.google.gerrit.client.reviewdb.ChangeAccess;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.StarredChange;
import com.google.gerrit.client.reviewdb.Change.Id;
import com.google.gerrit.client.rpc.NotSignedInException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.CookieAccess;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -77,7 +78,7 @@ public class ChangeListServiceImpl implements ChangeListService {
public void myStarredChanges(final AsyncCallback<List<ChangeInfo>> callback) {
final Account.Id me = idFromCookie();
if (me == null) {
callback.onFailure(new IllegalArgumentException("Not signed in"));
callback.onFailure(new NotSignedInException());
return;
}
try {
@@ -101,7 +102,7 @@ public class ChangeListServiceImpl implements ChangeListService {
final AsyncCallback<VoidResult> callback) {
final Account.Id me = idFromCookie();
if (me == null) {
callback.onFailure(new IllegalArgumentException("Not signed in"));
callback.onFailure(new NotSignedInException());
return;
}
@@ -148,7 +149,7 @@ public class ChangeListServiceImpl implements ChangeListService {
public void myStarredChangeIds(final AsyncCallback<Set<Id>> callback) {
final Account.Id me = idFromCookie();
if (me == null) {
callback.onFailure(new IllegalArgumentException("Not signed in"));
callback.onFailure(new NotSignedInException());
return;
}

View File

@@ -20,14 +20,13 @@ import com.google.gerrit.client.SignedInListener;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.Change.Id;
import com.google.gwt.core.client.GWT;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
@@ -42,7 +41,7 @@ import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwtjsonrpc.client.VoidCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -155,11 +154,7 @@ public class ChangeTable extends Composite implements HasFocus {
signedInListener = new SignedInListener() {
public void onSignIn() {
Util.LIST_SVC.myStarredChangeIds(new AsyncCallback<Set<Change.Id>>() {
public void onFailure(final Throwable caught) {
GWT.log("ChangeTable.onSignIn myStarredChangeIds failed", caught);
}
Util.LIST_SVC.myStarredChangeIds(new GerritCallback<Set<Change.Id>>() {
public void onSuccess(final Set<Change.Id> result) {
if (result != null) {
final int max = table.getRowCount();
@@ -197,12 +192,23 @@ public class ChangeTable extends Composite implements HasFocus {
protected void onStarClick(final int row) {
final ChangeInfo c = getChangeInfo(row);
if (c != null && Gerrit.isSignedIn()) {
c.setStarred(!c.isStarred());
final boolean prior = c.isStarred();
c.setStarred(!prior);
setStar(row, c);
final ToggleStarRequest req = new ToggleStarRequest();
req.toggle(c.getId(), c.isStarred());
Util.LIST_SVC.toggleStars(req, VoidCallback.INSTANCE);
Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
}
@Override
public void onFailure(final Throwable caught) {
super.onFailure(caught);
c.setStarred(prior);
setStar(row, c);
}
});
}
}

View File

@@ -15,10 +15,9 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.data.ChangeInfo;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.rpc.AsyncCallback;
import java.util.List;
@@ -41,15 +40,11 @@ public class MineStarredScreen extends AccountScreen {
public void onLoad() {
super.onLoad();
table.setSavePointerId(History.getToken());
Util.LIST_SVC.myStarredChanges(new AsyncCallback<List<ChangeInfo>>() {
Util.LIST_SVC.myStarredChanges(new GerritCallback<List<ChangeInfo>>() {
public void onSuccess(final List<ChangeInfo> result) {
starred.display(result);
table.finishDisplay();
}
public void onFailure(final Throwable caught) {
GWT.log("Fail", caught);
}
});
}
}

View File

@@ -0,0 +1,45 @@
// Copyright 2008 Google Inc.
//
// 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.rpc;
import com.google.gerrit.client.ErrorDialog;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtjsonrpc.client.ServerUnavailableException;
/** Abstract callback handling generic error conditions automatically */
public abstract class GerritCallback<T> implements AsyncCallback<T> {
public void onFailure(final Throwable caught) {
if (isNotSignedIn(caught)) {
new ErrorDialog(Util.C.errorNotSignedIn()).center();
} else if (caught instanceof ServerUnavailableException) {
new ErrorDialog(Util.C.errorServerUnavailable()).center();
} else {
GWT.log(getClass().getName() + " caught " + caught, caught);
new ErrorDialog(caught).center();
}
}
private static boolean isNotSignedIn(final Throwable caught) {
if (caught instanceof NotSignedInException) {
return true;
}
return caught instanceof RemoteJsonException
&& caught.getMessage().equals(NotSignedInException.MESSAGE);
}
}

View File

@@ -0,0 +1,24 @@
// Copyright 2008 Google Inc.
//
// 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.rpc;
/** Error stating the user must be signed-in in order to perform this action. */
public class NotSignedInException extends Exception {
public static final String MESSAGE = "Not Signed In";
public NotSignedInException() {
super(MESSAGE);
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2008 Google Inc.
//
// 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.rpc;
import com.google.gwt.i18n.client.Constants;
public interface RpcConstants extends Constants {
String errorNotSignedIn();
String errorServerUnavailable();
String errorRemoteJsonException();
}

View File

@@ -0,0 +1,3 @@
errorNotSignedIn = Not Signed In
errorServerUnavailable = Server Unavailable
errorRemoteJsonException = Server Error

View File

@@ -0,0 +1,21 @@
// Copyright 2008 Google Inc.
//
// 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.rpc;
import com.google.gwt.core.client.GWT;
public class Util {
public static final RpcConstants C = GWT.create(RpcConstants.class);
}

View File

@@ -80,6 +80,28 @@
}
/** Error Dialog **/
.gerrit-ErrorDialog {
margin: 10px 10px 10px 10px;
}
.gerrit-ErrorDialog-ErrorType {
font-weight: bold;
white-space: nowrap;
margin-bottom: 15px;
}
.gerrit-ErrorDialog-ErrorMessage {
}
.gerrit-ErrorDialog-Buttons {
width: 100%;
margin-top: 15px;
text-align: right;
}
/** Screen **/
.gerrit-Screen {
}