Bug 1048162 Part 2 - Display an error message if fetching an email link fails r=standard8,darrin
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -166,22 +166,20 @@ loop.conversationViews = (function(mozL1
});
return (
ConversationDetailView({contact: this.props.contact},
React.DOM.p({className: "btn-label"}, pendingStateString),
React.DOM.div({className: "btn-group call-action-group"},
- React.DOM.div({className: "fx-embedded-call-button-spacer"}),
- React.DOM.button({className: btnCancelStyles,
- onClick: this.cancelCall},
- mozL10n.get("initiate_call_cancel_button")
- ),
- React.DOM.div({className: "fx-embedded-call-button-spacer"})
+ React.DOM.button({className: btnCancelStyles,
+ onClick: this.cancelCall},
+ mozL10n.get("initiate_call_cancel_button")
+ )
)
)
);
}
});
/**
@@ -189,60 +187,86 @@ loop.conversationViews = (function(mozL1
*/
var CallFailedView = React.createClass({displayName: 'CallFailedView',
mixins: [Backbone.Events],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
store: React.PropTypes.instanceOf(
loop.store.ConversationStore).isRequired,
- contact: React.PropTypes.object.isRequired
+ contact: React.PropTypes.object.isRequired,
+ // This is used by the UI showcase.
+ emailLinkError: React.PropTypes.bool,
},
getInitialState: function() {
- return {emailLinkButtonDisabled: false};
+ return {
+ emailLinkError: this.props.emailLinkError,
+ emailLinkButtonDisabled: false
+ };
},
componentDidMount: function() {
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
+ this.listenTo(this.props.store, "error:emailLink",
+ this._onEmailLinkError);
},
componentWillUnmount: function() {
this.stopListening(this.props.store);
},
_onEmailLinkReceived: function() {
var emailLink = this.props.store.get("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
},
+ _onEmailLinkError: function() {
+ this.setState({
+ emailLinkError: true,
+ emailLinkButtonDisabled: false
+ });
+ },
+
+ _renderError: function() {
+ if (!this.state.emailLinkError) {
+ return;
+ }
+ return React.DOM.p({className: "error"}, mozL10n.get("unable_retrieve_url"));
+ },
+
retryCall: function() {
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
emailLink: function() {
- this.setState({emailLinkButtonDisabled: true});
+ this.setState({
+ emailLinkError: false,
+ emailLinkButtonDisabled: true
+ });
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
},
render: function() {
return (
React.DOM.div({className: "call-window"},
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")),
+ this._renderError(),
+
React.DOM.div({className: "btn-group call-action-group"},
React.DOM.button({className: "btn btn-cancel",
onClick: this.cancelCall},
mozL10n.get("cancel_button")
),
React.DOM.button({className: "btn btn-info btn-retry",
onClick: this.retryCall},
mozL10n.get("retry_call_button")
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -166,22 +166,20 @@ loop.conversationViews = (function(mozL1
});
return (
<ConversationDetailView contact={this.props.contact}>
<p className="btn-label">{pendingStateString}</p>
<div className="btn-group call-action-group">
- <div className="fx-embedded-call-button-spacer"></div>
- <button className={btnCancelStyles}
- onClick={this.cancelCall}>
- {mozL10n.get("initiate_call_cancel_button")}
- </button>
- <div className="fx-embedded-call-button-spacer"></div>
+ <button className={btnCancelStyles}
+ onClick={this.cancelCall}>
+ {mozL10n.get("initiate_call_cancel_button")}
+ </button>
</div>
</ConversationDetailView>
);
}
});
/**
@@ -189,60 +187,86 @@ loop.conversationViews = (function(mozL1
*/
var CallFailedView = React.createClass({
mixins: [Backbone.Events],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
store: React.PropTypes.instanceOf(
loop.store.ConversationStore).isRequired,
- contact: React.PropTypes.object.isRequired
+ contact: React.PropTypes.object.isRequired,
+ // This is used by the UI showcase.
+ emailLinkError: React.PropTypes.bool,
},
getInitialState: function() {
- return {emailLinkButtonDisabled: false};
+ return {
+ emailLinkError: this.props.emailLinkError,
+ emailLinkButtonDisabled: false
+ };
},
componentDidMount: function() {
this.listenTo(this.props.store, "change:emailLink",
this._onEmailLinkReceived);
+ this.listenTo(this.props.store, "error:emailLink",
+ this._onEmailLinkError);
},
componentWillUnmount: function() {
this.stopListening(this.props.store);
},
_onEmailLinkReceived: function() {
var emailLink = this.props.store.get("emailLink");
var contactEmail = _getPreferredEmail(this.props.contact).value;
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
window.close();
},
+ _onEmailLinkError: function() {
+ this.setState({
+ emailLinkError: true,
+ emailLinkButtonDisabled: false
+ });
+ },
+
+ _renderError: function() {
+ if (!this.state.emailLinkError) {
+ return;
+ }
+ return <p className="error">{mozL10n.get("unable_retrieve_url")}</p>;
+ },
+
retryCall: function() {
this.props.dispatcher.dispatch(new sharedActions.RetryCall());
},
cancelCall: function() {
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
},
emailLink: function() {
- this.setState({emailLinkButtonDisabled: true});
+ this.setState({
+ emailLinkError: false,
+ emailLinkButtonDisabled: true
+ });
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
},
render: function() {
return (
<div className="call-window">
<h2>{mozL10n.get("generic_failure_title")}</h2>
<p className="btn-label">{mozL10n.get("generic_failure_with_reason2")}</p>
+ {this._renderError()}
+
<div className="btn-group call-action-group">
<button className="btn btn-cancel"
onClick={this.cancelCall}>
{mozL10n.get("cancel_button")}
</button>
<button className="btn btn-info btn-retry"
onClick={this.retryCall}>
{mozL10n.get("retry_call_button")}
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -239,16 +239,22 @@
justify-content: space-between;
min-height: 230px;
}
.call-window > .btn-label {
text-align: center;
}
+.call-window > .error {
+ text-align: center;
+ color: #f00;
+ font-size: 90%;
+}
+
.call-action-group {
display: flex;
padding: 2.5em 4px 0 4px;
width: 100%;
}
.call-action-group > .btn {
height: 26px;
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -308,18 +308,17 @@ loop.store.ConversationStore = (function
* Fetches a new call URL intended to be sent over email when a contact
* can't be reached.
*/
fetchEmailLink: function() {
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
// a user-set string.
this.client.requestCallUrl("", function(err, callUrlData) {
if (err) {
- // XXX better error reporting in the UI
- console.error(err);
+ this.trigger("error:emailLink");
return;
}
this.set("emailLink", callUrlData.callUrl);
}.bind(this));
},
/**
* Obtains the outgoing call data from the server and handles the
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -283,16 +283,34 @@ describe("loop.conversationViews", funct
function() {
sandbox.stub(window, "close");
view = mountTestComponent();
store.set("emailLink", "http://fake.invalid/");
sinon.assert.calledOnce(window.close);
});
+
+ it("should display an error message in case email link retrieval failed",
+ function() {
+ view = mountTestComponent();
+
+ store.trigger("error:emailLink");
+
+ expect(view.getDOMNode().querySelector(".error")).not.eql(null);
+ });
+
+ it("should allow retrying to get a call url if it failed previously",
+ function() {
+ view = mountTestComponent();
+
+ store.trigger("error:emailLink");
+
+ expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(false);
+ });
});
describe("OngoingConversationView", function() {
function mountTestComponent(props) {
return TestUtils.renderIntoDocument(
loop.conversationViews.OngoingConversationView(props));
}
--- a/browser/components/loop/test/shared/conversationStore_test.js
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -653,18 +653,27 @@ describe("loop.store.ConversationStore",
client.requestCallUrl = function(callId, cb) {
cb(null, {callUrl: "http://fake.invalid/"});
};
dispatcher.dispatch(new sharedActions.FetchEmailLink());
expect(store.get("emailLink")).eql("http://fake.invalid/");
});
- // XXX bug 1048162 Part 2
- it.skip("should trigger an error in case of failure");
+ it("should trigger an error:emailLink event in case of failure",
+ function() {
+ var trigger = sandbox.stub(store, "trigger");
+ client.requestCallUrl = function(callId, cb) {
+ cb("error");
+ };
+ dispatcher.dispatch(new sharedActions.FetchEmailLink());
+
+ sinon.assert.calledOnce(trigger);
+ sinon.assert.calledWithExactly(trigger, "error:emailLink");
+ });
});
describe("Events", function() {
describe("Websocket progress", function() {
beforeEach(function() {
dispatcher.dispatch(
new sharedActions.ConnectCall({sessionData: fakeSessionData}));
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -307,16 +307,22 @@
),
Section({name: "CallFailedView"},
Example({summary: "Call Failed", dashed: "true",
style: {width: "260px", height: "265px"}},
React.DOM.div({className: "fx-embedded"},
CallFailedView({dispatcher: dispatcher})
)
+ ),
+ Example({summary: "Call Failed — with call URL error", dashed: "true",
+ style: {width: "260px", height: "265px"}},
+ React.DOM.div({className: "fx-embedded"},
+ CallFailedView({dispatcher: dispatcher, emailLinkError: true})
+ )
)
),
Section({name: "StartConversationView"},
Example({summary: "Start conversation view", dashed: "true"},
React.DOM.div({className: "standalone"},
StartConversationView({conversation: mockConversationModel,
client: mockClient,
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -308,16 +308,22 @@
<Section name="CallFailedView">
<Example summary="Call Failed" dashed="true"
style={{width: "260px", height: "265px"}}>
<div className="fx-embedded">
<CallFailedView dispatcher={dispatcher} />
</div>
</Example>
+ <Example summary="Call Failed — with call URL error" dashed="true"
+ style={{width: "260px", height: "265px"}}>
+ <div className="fx-embedded">
+ <CallFailedView dispatcher={dispatcher} emailLinkError={true} />
+ </div>
+ </Example>
</Section>
<Section name="StartConversationView">
<Example summary="Start conversation view" dashed="true">
<div className="standalone">
<StartConversationView conversation={mockConversationModel}
client={mockClient}
notifications={notifications} />