Resolve hook problems.

Fixed logging.
This commit is contained in:
RB 2020-05-26 22:35:55 -04:00
parent bc177cc1a1
commit 38efa6a030
14 changed files with 143 additions and 102 deletions

View File

@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- [x] Fixed hook that causes 100x rerendering.
- [x] Logging.
- [x] Error handling.
- [x] Table sorting.
- [x] Version check.
- [x] Fix JsonTree (yarn link).
@ -23,9 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [x] Monorepo for client/server.
- [x] Basic React/Apollo component.
## Tasks
### POC
### Next
- [ ] Webpack and dynamic config
- [ ] Complete WNS functionality
@ -38,8 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [ ] Signal
- [ ] Metadata
### Next
- [ ] https://github.com/standard/standardx (JSX)
- [ ] Client/server API abstraction (error handler, etc.)
- [ ] Port dashboard API calls (resolve config first).

View File

@ -30,8 +30,9 @@
"@apollo/react-components": "^3.1.5",
"@apollo/react-hooks": "^3.1.5",
"@babel/runtime": "^7.8.7",
"@dxos/debug": "^1.0.0-beta.20",
"@dxos/gem-core": "^1.0.0-beta.11",
"@dxos/react-ux": "^1.0.0-beta.20",
"@dxos/react-ux": "^1.0.0-beta.37",
"@material-ui/core": "^4.10.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.54",

View File

@ -2,7 +2,7 @@
// Copyright 2020 DxOS.org
//
import React from 'react';
import React, { useEffect, useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Alert from '@material-ui/lab/Alert';
import AlertTitle from '@material-ui/lab/AlertTitle';
@ -10,30 +10,37 @@ import Snackbar from '@material-ui/core/Snackbar';
const useStyles = makeStyles(() => ({
root: {
marginBottom: 60
marginBottom: 48
},
alert: {
minWidth: 400
}
}));
const Error = ({ message, ...rest }) => {
const Error = ({ error, ...rest }) => {
const classes = useStyles();
if (!message) {
return null;
}
const [message, setMessage] = useState(error);
const messages = Array.isArray(message) ? message : [message];
useEffect(() => {
setMessage(error ? error.message || String(error) : null);
}, [error]);
const handleClose = () => {
setMessage(null);
};
return (
<Snackbar
className={classes.root}
classes={{ root: classes.root }}
open={Boolean(message)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
TransitionProps={{ exit: false }}
autoHideDuration={1000}
>
<Alert severity="error" {...rest}>
<Alert classes={{ root: classes.alert }} severity="error" {...rest} onClose={handleClose}>
<AlertTitle>Error</AlertTitle>
{messages.map((message, i) => (
<div key={i}>{message}</div>
))}
<div>{message}</div>
</Alert>
</Snackbar>
);

View File

@ -4,31 +4,28 @@
import clsx from 'clsx';
import moment from 'moment';
import React, { Fragment } from 'react';
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flex: 1,
flexDirection: 'column',
overflow: 'hidden'
},
container: {
log: {
display: 'flex',
// Pin to bottom (render in time order).
flexDirection: 'column-reverse',
flex: 1,
overflowX: 'scroll',
overflowY: 'scroll'
},
log: {
overflow: 'scroll',
padding: theme.spacing(1),
fontSize: 16,
fontFamily: 'monospace',
whiteSpace: 'nowrap'
'& div': {
fontSize: 16,
fontFamily: 'monospace',
whiteSpace: 'nowrap'
}
},
level: {
@ -50,7 +47,7 @@ const useStyles = makeStyles(theme => ({
}
}));
const Log = ({ log = [], onClear }) => {
const Log = ({ log = [] }) => {
const classes = useStyles();
const levels = {
@ -91,11 +88,11 @@ const Log = ({ log = [], onClear }) => {
const pkg = levels[level] ? '' : `[${level}]: `;
message = (
<Fragment>
<>
<span className={classes.ts}>{datetime}</span>
<span className={clsx(classes.level, className)}>{label || level}</span>
<span>{pkg}{text}</span>
</Fragment>
</>
);
return true;
@ -111,10 +108,8 @@ const Log = ({ log = [], onClear }) => {
return (
<div className={classes.root}>
<div className={classes.container}>
<div className={classes.log}>
{log.reverse().map((line, i) => <Line key={i} message={line} />)}
</div>
<div className={classes.log}>
{log.map((line, i) => <Line key={i} message={line} />)}
</div>
</div>
);

View File

@ -2,7 +2,7 @@
// Copyright 2020 DxOS.org.org
//
import React, { Fragment } from 'react';
import React from 'react';
import Link from '@material-ui/core/Link';
import { getServiceUrl } from '../util/config';
@ -14,7 +14,6 @@ import { getServiceUrl } from '../util/config';
* @param {string} pkg
*/
const PackageLink = ({ config, type, pkg }) => {
// TODO(burdon): Pass in expected arg types.
if (typeof pkg === 'string') {
const ipfsUrl = getServiceUrl(config, 'ipfs.gateway', { path: `${pkg}` });
@ -25,28 +24,26 @@ const PackageLink = ({ config, type, pkg }) => {
switch (type) {
case 'wrn:bot': {
const packageLinks = [];
Object.keys(pkg).forEach(platform => {
Object.keys(pkg).forEach((platform, i) => {
Object.keys(pkg[platform]).forEach(arch => {
const cid = pkg[platform][arch];
const ipfsUrl = getServiceUrl(config, 'ipfs.gateway', { path: `${cid}` });
packageLinks.push(
<Fragment>
<Link
key={cid}
href={ipfsUrl}
title={cid}
target="ipfs"
>
{platform}/{arch}: {cid}
</Link>
</Fragment>
<Link
key={`${cid}`}
href={ipfsUrl}
title={cid}
target="ipfs"
>
{platform}/{arch}: {cid}
</Link>
);
});
});
return (
<Fragment>{packageLinks}</Fragment>
<>{packageLinks}</>
);
}
}

View File

@ -7,7 +7,7 @@ import defaultsDeep from 'lodash.defaultsdeep';
import ErrorBoundary from '../components/ErrorBoundary';
import { ConsoleContext, statusReducer, SET_STATUS } from '../hooks';
import { ConsoleContext, statusReducer, STATUS, SET_STATUS } from '../hooks';
const defaultState = {};
@ -18,8 +18,7 @@ const defaultState = {};
* @param {string} action
*/
const appReducer = (state, action) => ({
// TODO(burdon): Key shouldn't be same as action type.
[SET_STATUS]: statusReducer(state[SET_STATUS], action)
[STATUS]: statusReducer(state[STATUS], action)
});
/**
@ -36,8 +35,6 @@ const appReducer = (state, action) => ({
const ConsoleContextProvider = ({ children, config, modules, initialState = {}, errorHandler }) => {
const [state, dispatch] = useReducer(appReducer, defaultsDeep({}, initialState, defaultState));
const { errors: { exceptions = [] } = {} } = state[SET_STATUS] || {};
// Bind the error handler.
if (errorHandler) {
useEffect(() => {
@ -45,7 +42,7 @@ const ConsoleContextProvider = ({ children, config, modules, initialState = {},
dispatch({
type: SET_STATUS,
payload: {
exceptions: [error, ...exceptions]
error
}
});
});

View File

@ -9,6 +9,8 @@ import { ApolloProvider } from '@apollo/react-hooks';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ErrorHandler } from '@dxos/debug';
import config from '../../config.yml';
import { build } from '../../version.json';
@ -33,13 +35,16 @@ Object.assign(config, { build });
debug.enable(config.system.debug);
// Global error handler.
const errorHandler = new ErrorHandler();
/**
* Root application.
*/
const Main = () => {
return (
<ApolloProvider client={clientFactory(config)}>
<ConsoleContextProvider config={config} modules={modules}>
<ConsoleContextProvider config={config} modules={modules} errorHandler={errorHandler}>
<ThemeProvider theme={createTheme(config.app.theme)}>
<CssBaseline />
<HashRouter>

View File

@ -18,6 +18,8 @@ import red from '@material-ui/core/colors/red';
import { ConsoleContext, useStatusReducer } from '../hooks';
import Error from '../components/Error';
import VersionCheck from './VersionCheck';
const useStyles = makeStyles((theme) => ({
@ -30,17 +32,15 @@ const useStyles = makeStyles((theme) => ({
color: grey[400]
},
left: {
display: 'flex',
width: 160
},
right: {
display: 'flex',
width: 160,
textAlign: 'right'
justifyContent: 'flex-end'
},
center: {
flex: 1,
textAlign: 'center'
},
info: {
display: 'flex',
fontFamily: 'monospace',
fontSize: 'large',
@ -90,7 +90,7 @@ const StatusBar = () => {
const StatusIcon = ({ error }) => {
if (error) {
return (
<ErrorIcon className={clsx(classes.icon, classes.error)} title={String(error)} />
<ErrorIcon className={clsx(classes.icon, classes.error)} />
);
} else {
return (
@ -100,24 +100,28 @@ const StatusBar = () => {
};
return (
<Toolbar className={classes.root}>
<div className={classes.left}>
<Link className={classes.link} href={config.app.website} rel="noreferrer" target="_blank">
<PublicIcon />
</Link>
</div>
<>
<Toolbar className={classes.root}>
<div className={classes.left}>
<Link className={classes.link} href={config.app.website} rel="noreferrer" target="_blank">
<PublicIcon />
</Link>
</div>
<div className={classes.info}>
<div>{name} ({version})</div>
<div>{moment(buildDate).format('L')}</div>
<VersionCheck />
</div>
<div className={classes.center}>
<div>{name} ({version})</div>
<div>{moment(buildDate).format('L')}</div>
<VersionCheck />
</div>
<div className={classes.right}>
<LoadingIcon className={clsx(classes.icon, isLoading && classes.loading)} />
<StatusIcon error={error} />
</div>
</Toolbar>
<div className={classes.right}>
<LoadingIcon className={clsx(classes.icon, isLoading && classes.loading)} />
<StatusIcon error={error} />
</div>
</Toolbar>
<Error error={error} />
</>
);
};

View File

@ -20,8 +20,8 @@ import WNSLog from './WNSLog';
import WNSRecords, { WNSRecordType } from './WNSRecords';
import WNSStatus from './WNSStatus';
const TAB_RECORDS = 'explorer';
const TAB_STATUS = 'records';
const TAB_RECORDS = 'records';
const TAB_STATUS = 'status';
const TAB_LOG = 'log';
const useStyles = makeStyles(() => ({

View File

@ -2,20 +2,21 @@
// Copyright 2019 DxOS
//
import { useContext } from 'react';
import { useContext, useEffect } from 'react';
import { ConsoleContext } from './context';
export const SET_STATUS = 'errors';
export const STATUS = 'xxx';
export const SET_STATUS = 'set.status';
/**
*
* Dispatcher for app status.
*/
export const useStatusReducer = () => {
const { state, dispatch } = useContext(ConsoleContext);
return [
state[SET_STATUS] || {},
state[STATUS] || {},
value => dispatch({ type: SET_STATUS, payload: value || { exceptions: [] } })
];
};
@ -26,13 +27,15 @@ export const useStatusReducer = () => {
export const useQueryStatusReducer = ({ loading, error, data }) => {
const [, setStatus] = useStatusReducer();
if (loading) {
setTimeout(() => setStatus({ loading }));
}
useEffect(() => {
if (loading) {
setTimeout(() => setStatus({ loading }));
}
if (error) {
setTimeout(() => setStatus({ error }));
}
if (error) {
setTimeout(() => setStatus({ error }));
}
}, [loading, error]);
return data;
};

View File

@ -12,6 +12,8 @@ const log = debug('dxos:console:client:resolvers');
const timestamp = () => new Date().toUTCString();
const MAX_LOG_LENGTH = 200;
/**
* Resolvers
* https://www.apollographql.com/docs/tutorial/local-state/#local-resolvers
@ -21,6 +23,11 @@ export const createResolvers = config => {
const endpoint = getServiceUrl(config, 'wns.server', { absolute: true });
const registry = new Registry(endpoint);
// TODO(burdon): Errors swallowed!
// Oldest to latest.
let cachedLog = [];
return {
Query: {
wns_status: async () => {
@ -52,13 +59,29 @@ export const createResolvers = config => {
wns_log: async () => {
log('WNS log...');
// TODO(burdon): Cache and merge previous state.
const data = await registry.getLogs();
// TODO(burdon): Bug returns blank line at end.
const filtered = data.map(line => line).filter(Boolean);
// Cache and merge previous state.
let i = filtered.findIndex(line => line === cachedLog[cachedLog.length - 1]);
if (i === -1) {
cachedLog = filtered;
} else {
i++;
for (; i < filtered.length - 1; i++) {
cachedLog.push(filtered[i]);
}
// Trim.
cachedLog.splice(0, cachedLog.length - MAX_LOG_LENGTH);
}
return {
__typename: 'JSONLog',
timestamp: timestamp(),
log: data
log: [...cachedLog].reverse()
};
}
}

View File

@ -1,7 +1,7 @@
{
"build": {
"name": "@dxos/console-client",
"buildDate": "2020-05-26T17:38:45.688Z",
"buildDate": "2020-05-27T01:16:27.565Z",
"version": "1.0.0-beta.0"
}
}

View File

@ -15,6 +15,10 @@ const timestamp = () => new Date().toUTCString();
* @param config
*/
export const createResolvers = config => ({
// TODO(burdon): Auth mutations.
// https://www.apollographql.com/docs/apollo-server/data/errors/#codes
Mutation: {
//
// WNS

View File

@ -1179,6 +1179,13 @@
dependencies:
debug "^4.1.1"
"@dxos/debug@^1.0.0-beta.37":
version "1.0.0-beta.37"
resolved "https://registry.yarnpkg.com/@dxos/debug/-/debug-1.0.0-beta.37.tgz#0fc3a1d9d0b2616c5c22da6fcea7c369610ec282"
integrity sha512-65jOaqMrjv1Comepqq2U0k9olpAiKtk6pVEnhZHb+FutPMnjIkA+TPedNfTpxWQEs3MpG0IVyj64JugGl2KhQA==
dependencies:
debug "^4.1.1"
"@dxos/gem-core@^1.0.0-beta.11":
version "1.0.0-beta.11"
resolved "https://registry.yarnpkg.com/@dxos/gem-core/-/gem-core-1.0.0-beta.11.tgz#b4b8e82b3cfe7a9dd06fac8596ccf04fa8028e21"
@ -1194,17 +1201,16 @@
react-dom "^16.13.1"
react-resize-aware "^3.0.0"
"@dxos/react-ux@^1.0.0-beta.20":
version "1.0.0-beta.20"
resolved "https://registry.yarnpkg.com/@dxos/react-ux/-/react-ux-1.0.0-beta.20.tgz#0f0801ae2ddb9089428034cc4b466ee98206a180"
integrity sha512-larSB5cNCbyvP55/qan9uzioGXrcbtvrmy+fBRx7eLlsFs/eTgD/zrNMSVtq5mvMAn/oKKCOX1wipxm4gK6fdA==
"@dxos/react-ux@^1.0.0-beta.37":
version "1.0.0-beta.37"
resolved "https://registry.yarnpkg.com/@dxos/react-ux/-/react-ux-1.0.0-beta.37.tgz#4a6003ce52f0afa156e0af457162d2d14695f153"
integrity sha512-QuRIAc4xY57uHRqRnXwGZEW5VD6NVmpIQWRB1T2XsmRdyIzwWDHgK6pBzd7zeNQI/6nqzHr0fkeRY0Lsqou8Cw==
dependencies:
"@dxos/crypto" "^1.0.0-beta.1"
"@dxos/debug" "^1.0.0-beta.20"
"@dxos/debug" "^1.0.0-beta.37"
"@material-ui/core" "^4.9.0"
"@material-ui/icons" "^4.5.1"
"@material-ui/lab" "^4.0.0-alpha.42"
"@material-ui/styles" "^4.9.0"
clsx "^1.0.4"
lodash.isplainobject "^4.0.6"
uuid "^3.3.3"
@ -2189,7 +2195,7 @@
prop-types "^15.7.2"
react-is "^16.8.0"
"@material-ui/styles@^4.10.0", "@material-ui/styles@^4.9.0":
"@material-ui/styles@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071"
integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==