[MM-41891] Improve error and reload states (#2019)

This commit is contained in:
Devin Binnie 2022-03-18 10:16:12 -04:00 committed by GitHub
parent f79c445920
commit 3eb904cd67
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 23 deletions

View file

@ -118,3 +118,5 @@ export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width';
export const DISPATCH_GET_DESKTOP_SOURCES = 'dispatch-get-desktop-sources'; export const DISPATCH_GET_DESKTOP_SOURCES = 'dispatch-get-desktop-sources';
export const DESKTOP_SOURCES_RESULT = 'desktop-sources-result'; export const DESKTOP_SOURCES_RESULT = 'desktop-sources-result';
export const RELOAD_CURRENT_VIEW = 'reload-current-view';

View file

@ -105,12 +105,15 @@ describe('main/views/MattermostView', () => {
describe('retry', () => { describe('retry', () => {
const window = {on: jest.fn()}; const window = {on: jest.fn()};
const mattermostView = new MattermostView(tabView, {}, window, {}); const mattermostView = new MattermostView(tabView, {}, window, {});
const retryInBackgroundFn = jest.fn();
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers();
mattermostView.view.webContents.loadURL.mockImplementation(() => Promise.resolve()); mattermostView.view.webContents.loadURL.mockImplementation(() => Promise.resolve());
mattermostView.loadSuccess = jest.fn(); mattermostView.loadSuccess = jest.fn();
mattermostView.loadRetry = jest.fn(); mattermostView.loadRetry = jest.fn();
mattermostView.emit = jest.fn(); mattermostView.emit = jest.fn();
mattermostView.retryInBackground = () => retryInBackgroundFn;
}); });
it('should do nothing when webcontents are destroyed', () => { it('should do nothing when webcontents are destroyed', () => {
@ -140,7 +143,7 @@ describe('main/views/MattermostView', () => {
expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error); expect(mattermostView.loadRetry).toBeCalledWith('http://server-1.com', error);
}); });
it('should set to error status when max retries are reached', async () => { it('should set to error status and retry in the background when max retries are reached', async () => {
mattermostView.maxRetries = 0; mattermostView.maxRetries = 0;
const error = new Error('test'); const error = new Error('test');
const promise = Promise.reject(error); const promise = Promise.reject(error);
@ -151,6 +154,8 @@ describe('main/views/MattermostView', () => {
expect(mattermostView.loadRetry).not.toBeCalled(); expect(mattermostView.loadRetry).not.toBeCalled();
expect(WindowManager.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.name, expect.any(String), expect.any(String)); expect(WindowManager.sendToRenderer).toBeCalledWith(LOAD_FAILED, mattermostView.tab.name, expect.any(String), expect.any(String));
expect(mattermostView.status).toBe(-1); expect(mattermostView.status).toBe(-1);
jest.runAllTimers();
expect(retryInBackgroundFn).toBeCalled();
}); });
}); });

View file

@ -175,13 +175,27 @@ export class MattermostView extends EventEmitter {
} else { } else {
WindowManager.sendToRenderer(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString()); WindowManager.sendToRenderer(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
this.emit(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString()); this.emit(LOAD_FAILED, this.tab.name, err.toString(), loadURL.toString());
log.info(`[${Util.shorten(this.tab.name)}] Couldn't stablish a connection with ${loadURL}: ${err}.`); log.info(`[${Util.shorten(this.tab.name)}] Couldn't stablish a connection with ${loadURL}: ${err}. Will continue to retry in the background.`);
this.status = Status.ERROR; this.status = Status.ERROR;
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
} }
}); });
}; };
} }
retryInBackground = (loadURL: string) => {
return () => {
// window was closed while retrying
if (!this.view || !this.view.webContents) {
return;
}
const loading = this.view.webContents.loadURL(loadURL, {userAgent: composeUserAgent()});
loading.then(this.loadSuccess(loadURL)).catch(() => {
this.retryLoad = setTimeout(this.retryInBackground(loadURL), RELOAD_INTERVAL);
});
};
}
loadRetry = (loadURL: string, err: Error) => { loadRetry = (loadURL: string, err: Error) => {
this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL); this.retryLoad = setTimeout(this.retry(loadURL), RELOAD_INTERVAL);
WindowManager.sendToRenderer(LOAD_RETRY, this.tab.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString()); WindowManager.sendToRenderer(LOAD_RETRY, this.tab.name, Date.now() + RELOAD_INTERVAL, err.toString(), loadURL.toString());

View file

@ -98,7 +98,7 @@ export class ViewManager {
view.load(url); view.load(url);
view.on(UPDATE_TARGET_URL, this.showURLView); view.on(UPDATE_TARGET_URL, this.showURLView);
view.on(LOADSCREEN_END, this.finishLoading); view.on(LOADSCREEN_END, this.finishLoading);
view.once(LOAD_FAILED, this.failLoading); view.on(LOAD_FAILED, this.failLoading);
} }
reloadViewIfNeeded = (viewName: string) => { reloadViewIfNeeded = (viewName: string) => {

View file

@ -24,6 +24,7 @@ import {
BROWSER_HISTORY_BUTTON, BROWSER_HISTORY_BUTTON,
DISPATCH_GET_DESKTOP_SOURCES, DISPATCH_GET_DESKTOP_SOURCES,
DESKTOP_SOURCES_RESULT, DESKTOP_SOURCES_RESULT,
RELOAD_CURRENT_VIEW,
} from 'common/communication'; } from 'common/communication';
import urlUtils from 'common/utils/url'; import urlUtils from 'common/utils/url';
import {SECOND} from 'common/utils/constants'; import {SECOND} from 'common/utils/constants';
@ -68,6 +69,7 @@ export class WindowManager {
ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName); ipcMain.handle(GET_VIEW_NAME, this.handleGetViewName);
ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId); ipcMain.handle(GET_VIEW_WEBCONTENTS_ID, this.handleGetWebContentsId);
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources); ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
} }
handleUpdateConfig = () => { handleUpdateConfig = () => {
@ -650,6 +652,15 @@ export class WindowManager {
})); }));
}); });
} }
handleReloadCurrentView = () => {
const view = this.viewManager?.getCurrentView();
if (!view) {
return;
}
view?.reload();
this.viewManager?.showByName(view?.name);
}
} }
const windowManager = new WindowManager(); const windowManager = new WindowManager();

View file

@ -13,6 +13,7 @@ type Props = {
id?: string; id?: string;
active?: boolean; active?: boolean;
appName?: string; appName?: string;
handleLink: () => void;
}; };
export default function ErrorView(props: Props) { export default function ErrorView(props: Props) {
@ -42,27 +43,26 @@ export default function ErrorView(props: Props) {
> >
<h2>{`Cannot connect to ${props.appName}`}</h2> <h2>{`Cannot connect to ${props.appName}`}</h2>
<hr/> <hr/>
<p>{`We're having trouble connecting to ${props.appName}. If refreshing this page (Ctrl+R or Command+R) does not work please verify that:`}</p> <p>
{`We're having trouble connecting to ${props.appName}. We'll continue to try and establish a connection.`}
<br/> <br/>
{'If refreshing this page (Ctrl+R or Command+R) does not work please verify that:'}
</p>
<ul className='ErrorView-bullets' > <ul className='ErrorView-bullets' >
<li>{'Your computer is connected to the internet.'}</li> <li>{'Your computer is connected to the internet.'}</li>
<li>{`The ${props.appName} URL `} <li>{`The ${props.appName} URL `}
<a <a
//onClick={handleClick} onClick={props.handleLink}
href={props.url} href='#'
target='_blank'
rel='noreferrer'
> >
{props.url} {props.url}
</a>{' is correct.'}</li> </a>{' is correct.'}</li>
<li>{'You can reach '} <li>{'You can reach '}
<a <a
// onClick={handleClick} onClick={props.handleLink}
href={props.url} href='#'
target='_blank'
rel='noreferrer'
> >
{props.url} {props.url}
</a>{' from a browser window.'}</li> </a>{' from a browser window.'}</li>

View file

@ -44,6 +44,7 @@ import {
START_UPGRADE, START_UPGRADE,
START_DOWNLOAD, START_DOWNLOAD,
CLOSE_TAB, CLOSE_TAB,
RELOAD_CURRENT_VIEW,
} from 'common/communication'; } from 'common/communication';
import restoreButton from '../../assets/titlebar/chrome-restore.svg'; import restoreButton from '../../assets/titlebar/chrome-restore.svg';
@ -358,6 +359,10 @@ export default class MainPage extends React.PureComponent<Props, State> {
this.handleCloseTeamsDropdown(); this.handleCloseTeamsDropdown();
} }
reloadCurrentView = () => {
window.ipcRenderer.send(RELOAD_CURRENT_VIEW);
}
render() { render() {
const currentTabs = this.props.teams.find((team) => team.name === this.state.activeServerName)?.tabs || []; const currentTabs = this.props.teams.find((team) => team.name === this.state.activeServerName)?.tabs || [];
@ -528,16 +533,6 @@ export default class MainPage extends React.PureComponent<Props, State> {
return null; return null;
} }
switch (tabStatus.status) { switch (tabStatus.status) {
case Status.NOSERVERS: // TODO: substitute with https://mattermost.atlassian.net/browse/MM-25003
component = (
<ErrorView
id={'NoServers'}
errorInfo={'No Servers configured'}
url={tabStatus.extra ? tabStatus.extra.url : ''}
active={true}
appName={this.props.appName}
/>);
break;
case Status.FAILED: case Status.FAILED:
component = ( component = (
<ErrorView <ErrorView
@ -546,6 +541,7 @@ export default class MainPage extends React.PureComponent<Props, State> {
url={tabStatus.extra ? tabStatus.extra.url : ''} url={tabStatus.extra ? tabStatus.extra.url : ''}
active={true} active={true}
appName={this.props.appName} appName={this.props.appName}
handleLink={this.reloadCurrentView}
/>); />);
break; break;
case Status.LOADING: case Status.LOADING: