From ffbe97341cf8928bd2d64c6c3478726377ebeca4 Mon Sep 17 00:00:00 2001 From: Manuel Calavera Date: Thu, 12 Sep 2019 16:51:11 -0700 Subject: [PATCH] feat: failed fetch data state and logic --- src/AppV2.js | 15 ++++++++--- src/v2/api/socket.js | 5 +++- src/v2/assets/icons/warn.svg | 6 ++--- src/v2/components/EndpointSelector/index.jsx | 3 ++- src/v2/components/UI/FailedPanel/index.jsx | 28 ++++++++++++++++++++ src/v2/components/UI/FailedPanel/styles.js | 26 ++++++++++++++++++ src/v2/constants.js | 2 ++ src/v2/stores/nodes.js | 17 +++++++----- src/v2/stores/socket.js | 17 ++++++++++++ 9 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 src/v2/components/UI/FailedPanel/index.jsx create mode 100644 src/v2/components/UI/FailedPanel/styles.js diff --git a/src/AppV2.js b/src/AppV2.js index f177822f..1bc4605e 100644 --- a/src/AppV2.js +++ b/src/AppV2.js @@ -1,15 +1,17 @@ import {CssBaseline, makeStyles} from '@material-ui/core'; import {MuiThemeProvider} from '@material-ui/core/styles'; +import {observer} from 'mobx-react-lite'; import React, {lazy, Suspense} from 'react'; import {hot} from 'react-hot-loader/root'; import {Route, Switch} from 'react-router-dom'; import Header from 'v2/components/Header'; import Footer from 'v2/components/Footer'; import theme from 'v2/theme'; -import socket from 'v2/stores/socket'; +import FailedPanel from 'v2/components/UI/FailedPanel'; +import Socket from 'v2/stores/socket'; try { - socket.init(); + Socket.init(); } catch (err) { console.error('Socket init failed:', err); } @@ -35,6 +37,7 @@ const useStyles = makeStyles(theme => ({ root: { display: 'flex', overflow: 'hidden', + minHeight: '100vh', }, content: { marginLeft: 50, @@ -42,6 +45,8 @@ const useStyles = makeStyles(theme => ({ padding: '50px 24px 0 24px', maxWidth: 'calc(100% - 50px)', width: '100%', + display: 'flex', + flexDirection: 'column', [theme.breakpoints.down('sm')]: { marginLeft: 0, padding: 0, @@ -66,13 +71,17 @@ const useStyles = makeStyles(theme => ({ const App = () => { const classes = useStyles(); + const {hasError} = Socket; + return (
+
+ {hasError && } Loading...
}> @@ -104,4 +113,4 @@ const App = () => { ); }; -export default hot(App); +export default hot(observer(App)); diff --git a/src/v2/api/socket.js b/src/v2/api/socket.js index 9d58a1b3..098b1621 100644 --- a/src/v2/api/socket.js +++ b/src/v2/api/socket.js @@ -1,7 +1,10 @@ import RobustWebSocket from 'robust-websocket'; +import {CONNECTION_TIMEOUT} from 'v2/constants'; import * as EndpointConfig from '../../EndpointConfig'; export const initSocket = () => { - return new RobustWebSocket(EndpointConfig.getApiWebsocketUrl()); + return new RobustWebSocket(EndpointConfig.getApiWebsocketUrl(), null, { + timeout: CONNECTION_TIMEOUT, + }); }; diff --git a/src/v2/assets/icons/warn.svg b/src/v2/assets/icons/warn.svg index b330d025..5caf49d0 100644 --- a/src/v2/assets/icons/warn.svg +++ b/src/v2/assets/icons/warn.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/src/v2/components/EndpointSelector/index.jsx b/src/v2/components/EndpointSelector/index.jsx index 48f839a1..73d5678f 100644 --- a/src/v2/components/EndpointSelector/index.jsx +++ b/src/v2/components/EndpointSelector/index.jsx @@ -10,12 +10,13 @@ import useStyles from './styles'; const EndpointSelector = () => { const classes = useStyles(); - const {endpointName, updateEndpointName} = socketActions; + const {endpointName, updateEndpointName, setError} = socketActions; const handleEndpointChange = event => { const endpointName = event.target.value; EndpointConfig.setEndpointName(endpointName); updateEndpointName(endpointName); socketActions.init(); + setError(false); updateBaseUrl(); }; const endPointsList = EndpointConfig.getEndpoints(); diff --git a/src/v2/components/UI/FailedPanel/index.jsx b/src/v2/components/UI/FailedPanel/index.jsx new file mode 100644 index 00000000..7f8a4875 --- /dev/null +++ b/src/v2/components/UI/FailedPanel/index.jsx @@ -0,0 +1,28 @@ +import {Typography} from '@material-ui/core'; +import React from 'react'; +import {ReactComponent as WarnIcon} from 'v2/assets/icons/warn.svg'; + +import useStyles from './styles'; + +const FailedPanel = () => { + const classes = useStyles(); + return ( +
+
+ + + Data currently unavailable. Report issues on{' '} + + Github + + +
+
+ ); +}; + +export default FailedPanel; diff --git a/src/v2/components/UI/FailedPanel/styles.js b/src/v2/components/UI/FailedPanel/styles.js new file mode 100644 index 00000000..eef22a1e --- /dev/null +++ b/src/v2/components/UI/FailedPanel/styles.js @@ -0,0 +1,26 @@ +import {makeStyles} from '@material-ui/core'; +import getColor from 'v2/utils/getColor'; + +export default makeStyles(theme => ({ + root: { + paddingTop: 50, + marginBottom: 'auto', + }, + body: { + backgroundColor: getColor('grey')(theme), + padding: 15, + textTransform: 'uppercase', + display: 'flex', + alignItems: 'center', + color: getColor('pink')(theme), + '& svg': { + marginRight: 15, + }, + }, + text: { + fontWeight: 'bold', + '& a': { + color: getColor('main')(theme), + }, + }, +})); diff --git a/src/v2/constants.js b/src/v2/constants.js index 3c4ed337..c4256bf0 100644 --- a/src/v2/constants.js +++ b/src/v2/constants.js @@ -29,3 +29,5 @@ export const TDS_STAGES = [ duration: TDS_DEFAULT_STAGE_LENGTH_BLOCKS, }, ]; + +export const CONNECTION_TIMEOUT = 10000; diff --git a/src/v2/stores/nodes.js b/src/v2/stores/nodes.js index 7cc14d75..41932621 100644 --- a/src/v2/stores/nodes.js +++ b/src/v2/stores/nodes.js @@ -34,12 +34,17 @@ class Store { }; fetchClusterInfo = flow(function*() { - const res = yield API.getClusterInfo(); - const data = res.data; - this.network = data.network; - this.totalStaked = data.totalStaked; - this.supply = data.supply; - this.networkInflationRate = data.networkInflationRate; + try { + const res = yield API.getClusterInfo(); + const data = res.data; + this.network = data.network; + this.totalStaked = data.totalStaked; + this.supply = data.supply; + this.networkInflationRate = data.networkInflationRate; + return res; + } catch (e) { + throw e; + } }); get mapMarkers() { diff --git a/src/v2/stores/socket.js b/src/v2/stores/socket.js index c09253c1..60144dec 100644 --- a/src/v2/stores/socket.js +++ b/src/v2/stores/socket.js @@ -2,11 +2,13 @@ import {observable, decorate, action} from 'mobx'; import {initSocket} from 'v2/api/socket'; import networkOverview from 'v2/stores/networkOverview'; import nodes from 'v2/stores/nodes'; +import {CONNECTION_TIMEOUT} from 'v2/constants'; import * as EndpointConfig from '../../EndpointConfig'; const socketActions = { isLoading: false, + hasError: false, actions: { 'global-info': networkOverview.updateGlobalStats, blk: networkOverview.addBlock, @@ -15,6 +17,7 @@ const socketActions = { }, endpointName: EndpointConfig.getEndpointName(), onMessage(event) { + this.setError(false); this.setLoading(false); const {t, m} = JSON.parse(event.data); this.actions[t](m); @@ -27,20 +30,34 @@ const socketActions = { } this.ws = initSocket(); this.ws.onmessage = event => this.onMessage(event); + this.ws.onerror = () => { + this.setError(true); + }; + setTimeout(() => { + if (this.ws.readyState === 0) { + this.setError(true); + } + }, CONNECTION_TIMEOUT); }, updateEndpointName(endpointName) { + this.setLoading(false); this.endpointName = endpointName; }, setLoading(loading) { this.isLoading = loading; }, + setError(hasError) { + this.hasError = hasError; + }, }; decorate(socketActions, { endpointName: observable, updateEndpointName: action.bound, setLoading: action.bound, + setError: action.bound, isLoading: observable, + hasError: observable, }); export default socketActions;