mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-27 02:23:28 +08:00
fix(401/403): Unify error bars, query /account, improve reconnect flow
Summary: See https://paper.dropbox.com/doc/Sync-disabling-for-N1-URZmjVpSSxWFvjC62TiFI Test Plan: Tests incoming Reviewers: juan, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2959
This commit is contained in:
parent
b750b3a313
commit
8f29e1cfbe
13 changed files with 263 additions and 122 deletions
|
@ -11,9 +11,18 @@ export default class AccountErrorHeader extends React.Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.unsubscribe = AccountStore.listen(() => this._onAccountsChanged());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
this.unsubscribe = null;
|
||||
}
|
||||
}
|
||||
|
||||
getStateFromStores() {
|
||||
return {accounts: AccountStore.accounts()}
|
||||
}
|
||||
|
@ -22,9 +31,9 @@ export default class AccountErrorHeader extends React.Component {
|
|||
this.setState(this.getStateFromStores())
|
||||
}
|
||||
|
||||
_reconnect(account) {
|
||||
_reconnect(existingAccount) {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
ipc.send('command', 'application:add-account', account.provider);
|
||||
ipc.send('command', 'application:add-account', {existingAccount});
|
||||
}
|
||||
|
||||
_openPreferences() {
|
||||
|
@ -37,6 +46,18 @@ export default class AccountErrorHeader extends React.Component {
|
|||
shell.openExternal("https://support.nylas.com/hc/en-us/requests/new");
|
||||
}
|
||||
|
||||
_onCheckAgain = (event) => {
|
||||
const errorAccounts = this.state.accounts.filter(a => a.hasSyncStateError());
|
||||
this.setState({refreshing: true});
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
AccountStore.refreshHealthOfAccounts(errorAccounts.map(a => a.id)).finally(() => {
|
||||
if (!this.mounted) { return; }
|
||||
this.setState({refreshing: false});
|
||||
});
|
||||
}
|
||||
|
||||
renderErrorHeader(message, buttonName, actionCallback) {
|
||||
return (
|
||||
<div className="account-error-header notifications-sticky">
|
||||
|
@ -52,11 +73,15 @@ export default class AccountErrorHeader extends React.Component {
|
|||
<div className="message">
|
||||
{message}
|
||||
</div>
|
||||
<a className="action refresh" onClick={this._onCheckAgain}>
|
||||
{this.state.refreshing ? "Checking..." : "Check Again"}
|
||||
</a>
|
||||
<a className="action default" onClick={actionCallback}>
|
||||
{buttonName}
|
||||
</a>
|
||||
</div>
|
||||
</div>)
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -93,6 +118,6 @@ export default class AccountErrorHeader extends React.Component {
|
|||
"Open preferences",
|
||||
() => this._openPreferences());
|
||||
}
|
||||
return false;
|
||||
return <span />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import {mount} from 'enzyme';
|
||||
import AccountErrorHeader from '../lib/headers/account-error-header';
|
||||
import {AccountStore, Account, Actions, React} from 'nylas-exports'
|
||||
import {ipcRenderer} from 'electron';
|
||||
|
||||
describe("AccountErrorHeader", function AccountErrorHeaderTests() {
|
||||
describe("when one account is in the `invalid` state", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(AccountStore, 'accounts').andReturn([
|
||||
new Account({id: 'A', syncState: 'invalid', emailAddress: '123@gmail.com'}),
|
||||
new Account({id: 'B', syncState: 'running', emailAddress: 'other@gmail.com'}),
|
||||
])
|
||||
});
|
||||
|
||||
it("renders an error bar that mentions the account email", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
expect(header.find('.notifications-sticky-item')).toBeDefined();
|
||||
expect(header.find('.message').text().indexOf('123@gmail.com') > 0).toBe(true);
|
||||
});
|
||||
|
||||
it("allows the user to refresh the account", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
spyOn(AccountStore, 'refreshHealthOfAccounts').andReturn(Promise.resolve());
|
||||
header.find('.action.refresh').simulate('click');
|
||||
expect(AccountStore.refreshHealthOfAccounts).toHaveBeenCalledWith(['A']);
|
||||
});
|
||||
|
||||
it("allows the user to reconnect the account", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
spyOn(ipcRenderer, 'send');
|
||||
header.find('.action.default').simulate('click');
|
||||
expect(ipcRenderer.send).toHaveBeenCalledWith('command', 'application:add-account', {
|
||||
existingAccount: AccountStore.accounts()[0],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when more than one account is in the `invalid` state", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(AccountStore, 'accounts').andReturn([
|
||||
new Account({id: 'A', syncState: 'invalid', emailAddress: '123@gmail.com'}),
|
||||
new Account({id: 'B', syncState: 'invalid', emailAddress: 'other@gmail.com'}),
|
||||
])
|
||||
});
|
||||
|
||||
it("renders an error bar", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
expect(header.find('.notifications-sticky-item')).toBeDefined();
|
||||
});
|
||||
|
||||
it("allows the user to refresh the accounts", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
spyOn(AccountStore, 'refreshHealthOfAccounts').andReturn(Promise.resolve());
|
||||
header.find('.action.refresh').simulate('click');
|
||||
expect(AccountStore.refreshHealthOfAccounts).toHaveBeenCalledWith(['A', 'B']);
|
||||
});
|
||||
|
||||
it("allows the user to open preferences", () => {
|
||||
spyOn(Actions, 'switchPreferencesTab')
|
||||
spyOn(Actions, 'openPreferences')
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
header.find('.action.default').simulate('click');
|
||||
expect(Actions.openPreferences).toHaveBeenCalled();
|
||||
expect(Actions.switchPreferencesTab).toHaveBeenCalledWith('Accounts');
|
||||
});
|
||||
});
|
||||
|
||||
describe("when all accounts are fine", () => {
|
||||
beforeEach(() => {
|
||||
spyOn(AccountStore, 'accounts').andReturn([
|
||||
new Account({id: 'A', syncState: 'running', emailAddress: '123@gmail.com'}),
|
||||
new Account({id: 'B', syncState: 'running', emailAddress: 'other@gmail.com'}),
|
||||
])
|
||||
});
|
||||
|
||||
it("renders nothing", () => {
|
||||
const header = mount(<AccountErrorHeader />);
|
||||
expect(header.html()).toEqual('<span></span>');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,17 +10,16 @@ url = require 'url'
|
|||
class AccountChoosePage extends React.Component
|
||||
@displayName: "AccountChoosePage"
|
||||
|
||||
constructor: (@props) ->
|
||||
@state =
|
||||
email: ""
|
||||
provider: ""
|
||||
|
||||
componentWillUnmount: ->
|
||||
@_usub?()
|
||||
|
||||
componentDidMount: ->
|
||||
if @props.pageData.provider
|
||||
providerData = _.findWhere(Providers, name: @props.pageData.provider)
|
||||
{existingAccount} = @props.pageData
|
||||
if existingAccount and not existingAccount.routed
|
||||
# Hack to prevent coming back to this page from drilling you back in.
|
||||
# This should all get re-written soon.
|
||||
existingAccount.routed = true
|
||||
|
||||
providerName = existingAccount.provider
|
||||
providerName = 'exchange' if providerName is 'eas'
|
||||
providerData = _.findWhere(Providers, {name: providerName})
|
||||
if providerData
|
||||
@_onChooseProvider(providerData)
|
||||
|
||||
|
@ -46,16 +45,6 @@ class AccountChoosePage extends React.Component
|
|||
<span className="provider-name">{provider.displayName}</span>
|
||||
</div>
|
||||
|
||||
_renderError: ->
|
||||
if @state.error
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{@state.error}
|
||||
</div>
|
||||
else <div></div>
|
||||
|
||||
_onEmailChange: (event) =>
|
||||
@setState email: event.target.value
|
||||
|
||||
_onChooseProvider: (provider) =>
|
||||
Actions.recordUserEvent('Auth Flow Started', {
|
||||
provider: provider.name
|
||||
|
@ -67,7 +56,7 @@ class AccountChoosePage extends React.Component
|
|||
_.delay =>
|
||||
@_onBounceToGmail(provider)
|
||||
, 600
|
||||
OnboardingActions.moveToPage("account-settings", {provider})
|
||||
OnboardingActions.moveToPage("account-settings", Object.assign({provider}, @props.pageData))
|
||||
|
||||
_base64url: (buf) ->
|
||||
# Python-style urlsafe_b64encode
|
||||
|
|
|
@ -26,6 +26,10 @@ class AccountSettingsPage extends React.Component
|
|||
if field.default?
|
||||
@state.settings[field.name] = field.default
|
||||
|
||||
if @props.pageData.existingAccount
|
||||
@state.fields.name = @props.pageData.existingAccount.name
|
||||
@state.fields.email = @props.pageData.existingAccount.emailAddress
|
||||
|
||||
# Special case for gmail. Rather than showing a form, we poll in the
|
||||
# background for completion of the gmail auth on the server.
|
||||
if @state.provider.name is 'gmail'
|
||||
|
@ -56,6 +60,10 @@ class AccountSettingsPage extends React.Component
|
|||
)
|
||||
poll(pollAttemptId,5000)
|
||||
|
||||
componentWillUnmount: =>
|
||||
clearTimeout(@_resizeTimer) if @_resizeTimer
|
||||
@_resizeTimer = null
|
||||
|
||||
render: ->
|
||||
<div className="page account-setup">
|
||||
<div className="logo-container">
|
||||
|
@ -67,7 +75,7 @@ class AccountSettingsPage extends React.Component
|
|||
|
||||
{@_renderTitle()}
|
||||
|
||||
<div className="back" onClick={@_fireMoveToPrevPage}>
|
||||
<div className="back" onClick={@_onMoveToPrevPage}>
|
||||
<RetinaImg
|
||||
name="onboarding-back.png"
|
||||
mode={RetinaImg.Mode.ContentPreserve}/>
|
||||
|
@ -447,11 +455,12 @@ class AccountSettingsPage extends React.Component
|
|||
callback()
|
||||
|
||||
_resize: =>
|
||||
setTimeout( =>
|
||||
clearTimeout(@_resizeTimer) if @_resizeTimer
|
||||
@_resizeTimer = setTimeout( =>
|
||||
@props.onResize?()
|
||||
, 10)
|
||||
|
||||
_fireMoveToPrevPage: =>
|
||||
_onMoveToPrevPage: =>
|
||||
if @state.pageNumber > 0
|
||||
@setState(pageNumber: @state.pageNumber - 1)
|
||||
@_resize()
|
||||
|
|
|
@ -119,13 +119,13 @@ Providers = [
|
|||
}
|
||||
]
|
||||
settings: [{
|
||||
name: 'password'
|
||||
type: 'password'
|
||||
placeholder: 'Password'
|
||||
label: 'Password'
|
||||
required: true
|
||||
page: 0
|
||||
}]
|
||||
name: 'password'
|
||||
type: 'password'
|
||||
placeholder: 'Password'
|
||||
label: 'Password'
|
||||
required: true
|
||||
page: 0
|
||||
}]
|
||||
}, {
|
||||
name: 'yahoo'
|
||||
displayName: 'Yahoo'
|
||||
|
@ -158,7 +158,7 @@ Providers = [
|
|||
required: true
|
||||
}]
|
||||
}, {
|
||||
name: 'imap'
|
||||
name: 'custom'
|
||||
displayName: 'IMAP / SMTP Setup'
|
||||
icon: 'ic-settings-account-imap.png'
|
||||
header_icon: 'setup-icon-provider-imap.png'
|
||||
|
@ -260,37 +260,6 @@ Providers = [
|
|||
required: true
|
||||
}
|
||||
]
|
||||
# }, {
|
||||
# name: 'default'
|
||||
# displayName: ''
|
||||
# icon: ''
|
||||
# color: ''
|
||||
# fields: [
|
||||
# {
|
||||
# name: 'name'
|
||||
# type: 'text'
|
||||
# placeholder: 'Ashton Letterman'
|
||||
# label: 'Name'
|
||||
# }, {
|
||||
# name: 'email'
|
||||
# type: 'text'
|
||||
# placeholder: 'you@example.com'
|
||||
# label: 'Email'
|
||||
# }
|
||||
# ]
|
||||
# settings: [
|
||||
# {
|
||||
# name: 'username'
|
||||
# type: 'text'
|
||||
# placeholder: 'Username'
|
||||
# label: 'Username'
|
||||
# }, {
|
||||
# name: 'password'
|
||||
# type: 'password'
|
||||
# placeholder: 'Password'
|
||||
# label: 'Password'
|
||||
# }
|
||||
# ]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint global-require: 0 */
|
||||
import _ from 'underscore';
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {EditableList, NewsletterSignup} from 'nylas-component-kit';
|
||||
import {RegExpUtils, Account} from 'nylas-exports';
|
||||
|
@ -57,8 +56,8 @@ class PreferencesAccountDetails extends Component {
|
|||
};
|
||||
|
||||
_setState = (updates, callback = () => {}) => {
|
||||
const updated = _.extend({}, this.state.account, updates);
|
||||
this.setState({account: updated}, callback);
|
||||
const account = Object.assign(this.state.account.clone(), updates);
|
||||
this.setState({account}, callback);
|
||||
};
|
||||
|
||||
_setStateAndSave = (updates) => {
|
||||
|
@ -106,12 +105,12 @@ class PreferencesAccountDetails extends Component {
|
|||
this._setStateAndSave({defaultAlias});
|
||||
};
|
||||
|
||||
_reconnect() {
|
||||
const {account} = this.state;
|
||||
_onReconnect = () => {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
ipc.send('command', 'application:add-account', account.provider);
|
||||
ipc.send('command', 'application:add-account', {existingAccount: this.state.account});
|
||||
}
|
||||
_contactSupport() {
|
||||
|
||||
_onContactSupport = () => {
|
||||
const {shell} = require("electron");
|
||||
shell.openExternal("https://support.nylas.com/hc/en-us/requests/new");
|
||||
}
|
||||
|
@ -152,17 +151,17 @@ class PreferencesAccountDetails extends Component {
|
|||
`Nylas N1 can no longer authenticate with ${account.emailAddress}. The password or
|
||||
authentication may have changed.`,
|
||||
"Reconnect",
|
||||
() => this._reconnect());
|
||||
this._onReconnect);
|
||||
case Account.SYNC_STATE_STOPPED:
|
||||
return this._renderErrorDetail(
|
||||
`The cloud sync for ${account.emailAddress} has been disabled. Please contact Nylas support.`,
|
||||
"Contact support",
|
||||
() => this._contactSupport());
|
||||
this._onContactSupport);
|
||||
default:
|
||||
return this._renderErrorDetail(
|
||||
`Nylas encountered an error while syncing mail for ${account.emailAddress}. Contact Nylas support for details.`,
|
||||
"Contact support",
|
||||
() => this._contactSupport());
|
||||
this._onContactSupport);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -176,7 +175,7 @@ class PreferencesAccountDetails extends Component {
|
|||
|
||||
return (
|
||||
<div className="account-details">
|
||||
{this._renderSyncErrorDetails(account)}
|
||||
{this._renderSyncErrorDetails()}
|
||||
<h3>Account Label</h3>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -185,6 +184,12 @@ class PreferencesAccountDetails extends Component {
|
|||
onChange={this._onAccountLabelUpdated}
|
||||
/>
|
||||
|
||||
<h3>Account Settings</h3>
|
||||
|
||||
<div className="btn" onClick={this._onReconnect}>
|
||||
{account.provider === 'gmail' ? 'Re-authenticate with Gmail...' : 'Update Connection Settings...'}
|
||||
</div>
|
||||
|
||||
<h3>Aliases</h3>
|
||||
|
||||
<div className="platform-note">
|
||||
|
@ -207,7 +212,6 @@ class PreferencesAccountDetails extends Component {
|
|||
<NewsletterSignup emailAddress={account.emailAddress} name={account.name} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,17 +62,16 @@ class PreferencesAccountList extends Component {
|
|||
return <div className="account-list"></div>;
|
||||
}
|
||||
return (
|
||||
<div className="account-list">
|
||||
<EditableList
|
||||
items={this.props.accounts}
|
||||
itemContent={this._renderAccount}
|
||||
selected={this.props.selected}
|
||||
onReorderItem={this.props.onReorderAccount}
|
||||
onCreateItem={this.props.onAddAccount}
|
||||
onSelectItem={this.props.onSelectAccount}
|
||||
onDeleteItem={this.props.onRemoveAccount}
|
||||
/>
|
||||
</div>
|
||||
<EditableList
|
||||
className="account-list"
|
||||
items={this.props.accounts}
|
||||
itemContent={this._renderAccount}
|
||||
selected={this.props.selected}
|
||||
onReorderItem={this.props.onReorderAccount}
|
||||
onCreateItem={this.props.onAddAccount}
|
||||
onSelectItem={this.props.onSelectAccount}
|
||||
onDeleteItem={this.props.onRemoveAccount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,13 @@
|
|||
justify-content: center;
|
||||
|
||||
.account-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
width: 400px;
|
||||
|
||||
.items-wrapper {
|
||||
height: 440px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.account {
|
||||
|
@ -49,7 +52,7 @@
|
|||
|
||||
.account-details {
|
||||
width: 400px;
|
||||
padding-top: 20px;
|
||||
padding: 20px;
|
||||
padding-left: @spacing-standard * 2.25;
|
||||
padding-right: @spacing-standard * 2.25;
|
||||
background-color: @gray-lighter;
|
||||
|
|
|
@ -152,18 +152,17 @@ describe "NylasAPI", ->
|
|||
expect(DatabaseTransaction.prototype.unpersistModel).not.toHaveBeenCalled()
|
||||
|
||||
describe "handleAuthenticationFailure", ->
|
||||
it "should post a notification", ->
|
||||
spyOn(Actions, 'postNotification')
|
||||
NylasAPI._handleAuthenticationFailure('/threads/1234', 'token')
|
||||
expect(Actions.postNotification).toHaveBeenCalled()
|
||||
expect(Actions.postNotification.mostRecentCall.args[0].message.trim()).toEqual("A mailbox action for your mail provider could not be completed. You may not be able to send or receive mail.")
|
||||
|
||||
it "should include the email address if possible", ->
|
||||
it "should put the account in an `invalid` state", ->
|
||||
spyOn(Actions, 'updateAccount')
|
||||
spyOn(AccountStore, 'tokenForAccountId').andReturn('token')
|
||||
spyOn(Actions, 'postNotification')
|
||||
NylasAPI._handleAuthenticationFailure('/threads/1234', 'token')
|
||||
expect(Actions.postNotification).toHaveBeenCalled()
|
||||
expect(Actions.postNotification.mostRecentCall.args[0].message.trim()).toEqual("A mailbox action for #{AccountStore.accounts()[0].emailAddress} could not be completed. You may not be able to send or receive mail.")
|
||||
expect(Actions.updateAccount).toHaveBeenCalled()
|
||||
expect(Actions.updateAccount.mostRecentCall.args).toEqual([AccountStore.accounts()[0].id, {syncState: 'invalid'}])
|
||||
|
||||
it "should not throw an exception if the account cannot be found", ->
|
||||
spyOn(Actions, 'updateAccount')
|
||||
NylasAPI._handleAuthenticationFailure('/threads/1234', 'token')
|
||||
expect(Actions.updateAccount).not.toHaveBeenCalled()
|
||||
|
||||
describe "handleModelResponse", ->
|
||||
beforeEach ->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
_ = require 'underscore'
|
||||
keytar = require 'keytar'
|
||||
NylasAPI = require '../../src/flux/nylas-api'
|
||||
AccountStore = require '../../src/flux/stores/account-store'
|
||||
Account = require('../../src/flux/models/account').default
|
||||
Actions = require '../../src/flux/actions'
|
||||
|
@ -38,6 +39,7 @@ describe "AccountStore", ->
|
|||
}]
|
||||
|
||||
spyOn(NylasEnv.config, 'get').andCallFake (key) =>
|
||||
return 'production' if key is 'env'
|
||||
return @configAccounts if key is 'nylas.accounts'
|
||||
return @configVersion if key is 'nylas.accountsVersion'
|
||||
return @configTokens if key is 'nylas.accountTokens'
|
||||
|
@ -77,6 +79,21 @@ describe "AccountStore", ->
|
|||
expect(@instance.tokenForAccountId('A')).toEqual('A-TOKEN')
|
||||
expect(@instance.tokenForAccountId('B')).toEqual('B-TOKEN')
|
||||
|
||||
describe "in the work window and running on production", ->
|
||||
it "should refresh the accounts", ->
|
||||
spyOn(NylasEnv, 'isWorkWindow').andReturn(true)
|
||||
@instance = new @constructor
|
||||
spyOn(@instance, 'refreshHealthOfAccounts')
|
||||
advanceClock(10000)
|
||||
expect(@instance.refreshHealthOfAccounts).toHaveBeenCalledWith(['A', 'B'])
|
||||
|
||||
describe "in the main window", ->
|
||||
it "should not refresh the accounts", ->
|
||||
@instance = new @constructor
|
||||
spyOn(@instance, 'refreshHealthOfAccounts')
|
||||
advanceClock(10000)
|
||||
expect(@instance.refreshHealthOfAccounts).not.toHaveBeenCalled()
|
||||
|
||||
describe "accountForEmail", ->
|
||||
beforeEach ->
|
||||
@instance = new @constructor
|
||||
|
@ -164,3 +181,45 @@ describe "AccountStore", ->
|
|||
expect(@instance._accounts.length).toBe 2
|
||||
expect(@instance.accountForId('B')).toBe(undefined)
|
||||
expect(@instance.accountForId('NEVER SEEN BEFORE')).not.toBe(undefined)
|
||||
|
||||
describe "refreshHealthOfAccounts", ->
|
||||
beforeEach ->
|
||||
@spyOnConfig()
|
||||
spyOn(NylasAPI, 'makeRequest').andCallFake (options) =>
|
||||
if options.accountId is 'return-api-error'
|
||||
Promise.reject(new Error("API ERROR"))
|
||||
else
|
||||
Promise.resolve({
|
||||
sync_state: 'running',
|
||||
id: options.accountId,
|
||||
account_id: options.accountId
|
||||
})
|
||||
@instance = new @constructor
|
||||
spyOn(@instance, '_save')
|
||||
|
||||
it "should GET /account for each of the provided account IDs", ->
|
||||
@instance.refreshHealthOfAccounts(['A', 'B'])
|
||||
expect(NylasAPI.makeRequest.callCount).toBe(2)
|
||||
expect(NylasAPI.makeRequest.calls[0].args).toEqual([{path: '/account', accountId: 'A'}])
|
||||
expect(NylasAPI.makeRequest.calls[1].args).toEqual([{path: '/account', accountId: 'B'}])
|
||||
|
||||
it "should update existing account objects and call save exactly once", ->
|
||||
@instance.accountForId('A').syncState = 'invalid'
|
||||
@instance.refreshHealthOfAccounts(['A', 'B'])
|
||||
advanceClock()
|
||||
expect(@instance.accountForId('A').syncState).toEqual('running')
|
||||
expect(@instance._save.callCount).toBe(1)
|
||||
|
||||
it "should ignore accountIds which do not exist locally when the request completes", ->
|
||||
@instance.accountForId('A').syncState = 'invalid'
|
||||
@instance.refreshHealthOfAccounts(['gone', 'A', 'B'])
|
||||
advanceClock()
|
||||
expect(@instance.accountForId('A').syncState).toEqual('running')
|
||||
expect(@instance._save.callCount).toBe(1)
|
||||
|
||||
it "should not stop if a single GET /account fails", ->
|
||||
@instance.accountForId('B').syncState = 'invalid'
|
||||
@instance.refreshHealthOfAccounts(['return-api-error', 'B']).catch (e) =>
|
||||
advanceClock()
|
||||
expect(@instance.accountForId('B').syncState).toEqual('running')
|
||||
expect(@instance._save.callCount).toBe(1)
|
||||
|
|
|
@ -300,12 +300,12 @@ export default class Application extends EventEmitter {
|
|||
win.browserWindow.inspectElement(x, y);
|
||||
});
|
||||
|
||||
this.on('application:add-account', (provider) => {
|
||||
this.on('application:add-account', ({existingAccount} = {}) => {
|
||||
this.windowManager.ensureWindow(WindowManager.ONBOARDING_WINDOW, {
|
||||
title: "Add an Account",
|
||||
windowProps: {
|
||||
page: "account-choose",
|
||||
pageData: {provider},
|
||||
pageData: {existingAccount},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
|
|
@ -250,24 +250,8 @@ class NylasAPI
|
|||
account = AccountStore.accounts().find (account) ->
|
||||
AccountStore.tokenForAccountId(account.id) is apiToken
|
||||
|
||||
email = "your mail provider"
|
||||
email = account.emailAddress if account
|
||||
|
||||
Actions.postNotification
|
||||
type: 'error'
|
||||
tag: '401'
|
||||
sticky: true
|
||||
message: "A mailbox action for #{email} could not be completed. You
|
||||
may not be able to send or receive mail.",
|
||||
icon: 'fa-sign-out'
|
||||
actions: [{
|
||||
default: true
|
||||
dismisses: true
|
||||
label: 'Dismiss'
|
||||
provider: account?.provider ? ""
|
||||
id: '401:dismiss'
|
||||
}]
|
||||
|
||||
if account
|
||||
Actions.updateAccount(account.id, {syncState: Account.SYNC_STATE_AUTH_FAILED})
|
||||
return Promise.resolve()
|
||||
|
||||
# Returns a Promise that resolves when any parsed out models (if any)
|
||||
|
|
|
@ -5,6 +5,7 @@ Account = require('../models/account').default
|
|||
Utils = require '../models/utils'
|
||||
DatabaseStore = require './database-store'
|
||||
keytar = require 'keytar'
|
||||
NylasAPI = null
|
||||
|
||||
configAccountsKey = "nylas.accounts"
|
||||
configVersionKey = "nylas.accountsVersion"
|
||||
|
@ -25,6 +26,11 @@ class AccountStore extends NylasStore
|
|||
@listenTo Actions.updateAccount, @_onUpdateAccount
|
||||
@listenTo Actions.reorderAccount, @_onReorderAccount
|
||||
|
||||
if NylasEnv.isWorkWindow() and ['staging', 'production'].includes(NylasEnv.config.get('env'))
|
||||
setTimeout( =>
|
||||
@refreshHealthOfAccounts(@_accounts.map((a) -> a.id))
|
||||
, 2000)
|
||||
|
||||
NylasEnv.config.onDidChange configVersionKey, (change) =>
|
||||
# If we already have this version of the accounts config, it means we
|
||||
# are the ones who saved the change, and we don't need to reload.
|
||||
|
@ -168,6 +174,20 @@ class AccountStore extends NylasStore
|
|||
@_save()
|
||||
Actions.focusDefaultMailboxPerspectiveForAccounts([account.id])
|
||||
|
||||
refreshHealthOfAccounts: (accountIds) =>
|
||||
NylasAPI ?= require '../nylas-api'
|
||||
Promise.all(accountIds.map (accountId) =>
|
||||
return NylasAPI.makeRequest({
|
||||
path: '/account',
|
||||
accountId: accountId,
|
||||
}).then (json) =>
|
||||
existing = @accountForId(accountId)
|
||||
return unless existing # user may have deleted
|
||||
existing.fromJSON(json)
|
||||
).finally =>
|
||||
@_caches = {}
|
||||
@_save()
|
||||
|
||||
# Exposed Data
|
||||
|
||||
# Private: Helper which caches the results of getter functions often needed
|
||||
|
|
Loading…
Reference in a new issue