Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better error handling #797

Merged
merged 9 commits into from
Sep 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Dockerfile.demo
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ RUN ["pip", "install", "--disable-pip-version-check", "--find-links=dist", "visu
WORKDIR /home/visualdl/frontend
ENV SCOPE server
ENV PUBLIC_PATH /paddle/visualdl/demo
ENV API_URL /paddle/visualdl/demo/api
RUN ["./scripts/install.sh"]
RUN ["./scripts/build.sh"]

Expand All @@ -21,7 +20,6 @@ WORKDIR /home/visualdl
COPY --from=builder /home/visualdl/frontend/ ./
ENV NODE_ENV production
ENV PUBLIC_PATH /paddle/visualdl/demo
ENV API_URL /paddle/visualdl/demo/api
ENV PING_URL /ping
ENV DEMO true
ENV HOST 0.0.0.0
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"react-router-dom": "5.2.0",
"react-spinners": "0.9.0",
"react-toastify": "6.0.8",
"styled-components": "5.1.1",
"styled-components": "5.2.0",
"swr": "0.3.0",
"tippy.js": "6.2.6"
},
Expand Down
23 changes: 20 additions & 3 deletions frontend/packages/core/public/locales/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@
"description": "Possible reasons are:",
"title": "No visualized data."
},
"error-with-status": "A {{statusCode}} error occurred on server",
"error-without-status": "An error occurred on the server",
"page-not-found": "Page Not Found"
"error": "Error occurred",
"network-error": "Network Error",
"page-not-found": "Page Not Found",
"parse-error": "Parse Error",
"response-error": {
"400": "Bad Request",
"401": "Unauthorized",
"403": "Permission Denied",
"404": "Interface Does Not Exist",
"405": "Method Not Allowed",
"408": "Request Timeout",
"413": "Request Entity Too Large",
"414": "Request-URI Too Long",
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"unknown": "Server Error"
}
}
23 changes: 20 additions & 3 deletions frontend/packages/core/public/locales/zh/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@
"description": "有以下几种可能原因,请您参考相应解决方案:",
"title": "无可视化结果展示"
},
"error-with-status": "服务器发生了一个 {{statusCode}} 错误",
"error-without-status": "服务器发生了一个错误",
"page-not-found": "页面不存在"
"error": "发生错误",
"network-error": "网络错误",
"page-not-found": "页面不存在",
"parse-error": "解析失败",
"response-error": {
"400": "请求错误",
"401": "未授权",
"403": "没有权限",
"404": "接口不存在",
"405": "方法不允许",
"408": "请求超时",
"413": "请求体过大",
"414": "请求地址过长",
"500": "服务器内部错误",
"501": "服务器未实现",
"502": "服务器网关错误",
"503": "服务器不可用",
"504": "服务器网关超时",
"unknown": "服务器错误"
}
}
27 changes: 18 additions & 9 deletions frontend/packages/core/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import {Redirect, Route, BrowserRouter as Router, Switch, useLocation} from 'rea
import {headerHeight, position, size} from '~/utils/style';

import BodyLoading from '~/components/BodyLoading';
import ErrorBoundary from '~/components/ErrorBoundary';
import ErrorPage from '~/pages/error';
import {Helmet} from 'react-helmet';
import NProgress from 'nprogress';
import Navbar from '~/components/Navbar';
import {SWRConfig} from 'swr';
import {ToastContainer} from 'react-toastify';
import {fetcher} from '~/utils/fetch';
import init from '@visualdl/wasm';
import routes from '~/routes';
Expand Down Expand Up @@ -56,7 +59,7 @@ const Telemetry: FunctionComponent = () => {
};

const App: FunctionComponent = () => {
const {i18n} = useTranslation();
const {t, i18n} = useTranslation('errors');

const dir = useMemo(() => (i18n.language ? i18n.dir(i18n.language) : ''), [i18n]);

Expand Down Expand Up @@ -91,17 +94,23 @@ const App: FunctionComponent = () => {
<Header>
<Navbar />
</Header>
<Suspense fallback={<Progress />}>
<Switch>
<Redirect exact from="/" to={defaultRoute?.path ?? '/index'} />
{routers.map(route => (
<Route key={route.id} path={route.path} component={route.component} />
))}
</Switch>
</Suspense>
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<Progress />}>
<Switch>
<Redirect exact from="/" to={defaultRoute?.path ?? '/index'} />
{routers.map(route => (
<Route key={route.id} path={route.path} component={route.component} />
))}
<Route path="*">
<ErrorPage title={t('errors:page-not-found')} />
</Route>
</Switch>
</Suspense>
</ErrorBoundary>
</Router>
</Main>
)}
<ToastContainer />
</SWRConfig>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions frontend/packages/core/src/components/Audio.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import {
WithStyled,
Expand All @@ -13,12 +12,14 @@ import {
} from '~/utils/style';

import {AudioPlayer} from '~/utils/audio';
import type {BlobResponse} from '~/utils/fetch';
import Icon from '~/components/Icon';
import PuffLoader from 'react-spinners/PuffLoader';
import RangeSlider from '~/components/RangeSlider';
import Slider from 'react-rangeslider';
import SyncLoader from 'react-spinners/SyncLoader';
import Tippy from '@tippyjs/react';
import {fetcher} from '~/utils/fetch';
import mime from 'mime-types';
import moment from 'moment';
import {saveAs} from 'file-saver';
Expand Down Expand Up @@ -147,7 +148,7 @@ const Audio = React.forwardRef<AudioRef, AudioProps & WithStyled>(
({audioContext, src, cache, onLoading, onLoad, className}, ref) => {
const {t} = useTranslation('common');

const {data, error, loading} = useRequest<BlobResponse>(src ?? null, blobFetcher, {
const {data, error, loading} = useRequest<BlobResponse>(src ?? null, fetcher, {
dedupingInterval: cache ?? 2000
});

Expand Down
35 changes: 25 additions & 10 deletions frontend/packages/core/src/components/Error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,29 @@ const Wrapper = styled.div`

const reload = () => window.location.reload();

const ReadmeMap: Record<string, string> = {
zh: 'https://github.com/PaddlePaddle/VisualDL/blob/develop/README.md',
en: 'https://github.com/PaddlePaddle/VisualDL/blob/develop/README-en.md'
};

const UserGuideMap: Record<string, string> = {
zh: 'https://github.com/PaddlePaddle/VisualDL/blob/develop/docs/components/README.md',
en: 'https://github.com/PaddlePaddle/VisualDL/blob/develop/docs/components/UserGuide-en.md'
};

const I18nLink: FunctionComponent<{map: Record<string, string>}> = ({map, children}) => {
const {i18n} = useTranslation();
return (
<a
href={map[i18n.language] ?? map[String(i18n.options.fallbackLng)] ?? map.en}
target="_blank"
rel="noreferrer"
>
{children}
</a>
);
};

const Error: FunctionComponent<WithStyled> = ({className, children}) => {
const {t} = useTranslation('errors');

Expand All @@ -61,22 +84,14 @@ const Error: FunctionComponent<WithStyled> = ({className, children}) => {
<li>
<Trans i18nKey="errors:common.1">
Log files are not generated. Please refer to&nbsp;
<a href="https://github.com/PaddlePaddle/VisualDL" target="_blank" rel="noreferrer">
README
</a>
<I18nLink map={ReadmeMap}>README</I18nLink>
&nbsp;to create log files.
</Trans>
</li>
<li>
<Trans i18nKey="errors:common.2">
Log files are generated but data is not written yet. Please refer to&nbsp;
<a
href="https://github.com/PaddlePaddle/VisualDL/blob/develop/docs/components/README.md"
target="_blank"
rel="noreferrer"
>
VisualDL User Guide
</a>
<I18nLink map={UserGuideMap}>VisualDL User Guide</I18nLink>
&nbsp;to write visualized data.
</Trans>
</li>
Expand Down
24 changes: 24 additions & 0 deletions frontend/packages/core/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

class ErrorBoundary extends React.Component<{fallback: React.ReactNode}, {hasError: boolean; error: Error | null}> {
state = {
hasError: false,
error: null
};

static getDerivedStateFromError(error: Error) {
return {
hasError: true,
error
};
}

render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}

export default ErrorBoundary;
5 changes: 3 additions & 2 deletions frontend/packages/core/src/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {BlobResponse, blobFetcher} from '~/utils/fetch';
import React, {useImperativeHandle, useLayoutEffect, useState} from 'react';
import {WithStyled, primaryColor} from '~/utils/style';

import type {BlobResponse} from '~/utils/fetch';
import GridLoader from 'react-spinners/GridLoader';
import {fetcher} from '~/utils/fetch';
import mime from 'mime-types';
import {saveAs} from 'file-saver';
import useRequest from '~/hooks/useRequest';
Expand All @@ -21,7 +22,7 @@ const Image = React.forwardRef<ImageRef, ImageProps & WithStyled>(({src, cache,
const {t} = useTranslation('common');

const [url, setUrl] = useState('');
const {data, error, loading} = useRequest<BlobResponse>(src ?? null, blobFetcher, {
const {data, error, loading} = useRequest<BlobResponse>(src ?? null, fetcher, {
dedupingInterval: cache ?? 2000
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/core/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ const Navbar: FunctionComponent = () => {

const currentPath = useMemo(() => pathname.replace(PUBLIC_PATH, ''), [pathname]);

const navItems = useNavItems();
const [navItems] = useNavItems();
const [items, setItems] = useState<NavbarItemProps[]>([]);
useEffect(() => {
setItems(oldItems =>
Expand Down
10 changes: 10 additions & 0 deletions frontend/packages/core/src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {useCallback, useMemo} from 'react';

const useLocalStorage = (key: string) => {
const value = useMemo(() => window.localStorage.getItem(key), [key]);
const setter = useCallback((value: string) => window.localStorage.setItem(key, value), [key]);
const remover = useCallback(() => window.localStorage.removeItem(key), [key]);
return [value, setter, remover] as const;
};

export default useLocalStorage;
6 changes: 3 additions & 3 deletions frontend/packages/core/src/hooks/useNavItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const navMap = {
const useNavItems = () => {
const [components, setComponents] = useState<Route[]>([]);

const {data, mutate} = useRequest<(keyof typeof navMap)[]>('/components', fetcher, {
const {data, loading, error, mutate} = useRequest<(keyof typeof navMap)[]>('/components', fetcher, {
refreshInterval: components.length ? 61 * 1000 : 15 * 1000,
dedupingInterval: 14 * 1000,
errorRetryInterval: 15 * 1000,
Expand Down Expand Up @@ -59,9 +59,9 @@ const useNavItems = () => {

useEffect(() => {
setComponents(filterPages(routes));
}, [data, filterPages]);
}, [filterPages]);

return components;
return [components, loading, error] as const;
};

export default useNavItems;
12 changes: 12 additions & 0 deletions frontend/packages/core/src/hooks/useQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type {ParseOptions} from 'query-string';
import queryString from 'query-string';
import {useLocation} from 'react-router-dom';
import {useMemo} from 'react';

const useQuery = (options?: ParseOptions) => {
const location = useLocation();
const query = useMemo(() => queryString.parse(location.search, options), [location.search, options]);
return query;
};

export default useQuery;
30 changes: 21 additions & 9 deletions frontend/packages/core/src/hooks/useRequest.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
import type {ConfigInterface, keyInterface, responseInterface} from 'swr';
import {useEffect, useMemo} from 'react';
import useSWR, {ConfigInterface, keyInterface, responseInterface} from 'swr';

import ee from '~/utils/event';
import type {fetcherFn} from 'swr/dist/types';
import {toast} from 'react-toastify';
import useSWR from 'swr';

type Response<D, E> = responseInterface<D, E> & {
loading: boolean;
};

function useRequest<D = unknown, E = unknown>(key: keyInterface): Response<D, E>;
function useRequest<D = unknown, E = unknown>(key: keyInterface, fetcher?: fetcherFn<D>): Response<D, E>;
function useRequest<D = unknown, E = unknown>(
function useRequest<D = unknown, E extends Error = Error>(key: keyInterface): Response<D, E>;
function useRequest<D = unknown, E extends Error = Error>(key: keyInterface, fetcher?: fetcherFn<D>): Response<D, E>;
function useRequest<D = unknown, E extends Error = Error>(
key: keyInterface,
fetcher?: fetcherFn<D>,
config?: ConfigInterface<D, E, fetcherFn<D>>
): Response<D, E>;
function useRequest<D = unknown, E = unknown>(
function useRequest<D = unknown, E extends Error = Error>(
key: keyInterface,
fetcher?: fetcherFn<D>,
config?: ConfigInterface<D, E, fetcherFn<D>>
): Response<D, E> {
const {data, error, ...other} = useSWR<D, E>(key, fetcher, config);
const loading = useMemo(() => !!key && !data && !error, [key, data, error]);

useEffect(() => {
if (error) {
toast(error.message, {
position: toast.POSITION.TOP_CENTER,
type: toast.TYPE.ERROR
});
}
}, [error]);

return {data, error, loading, ...other};
}

function useRunningRequest<D = unknown, E = unknown>(key: keyInterface, running: boolean): Response<D, E>;
function useRunningRequest<D = unknown, E = unknown>(
function useRunningRequest<D = unknown, E extends Error = Error>(key: keyInterface, running: boolean): Response<D, E>;
function useRunningRequest<D = unknown, E extends Error = Error>(
key: keyInterface,
running: boolean,
fetcher?: fetcherFn<D>
): Response<D, E>;
function useRunningRequest<D = unknown, E = unknown>(
function useRunningRequest<D = unknown, E extends Error = Error>(
key: keyInterface,
running: boolean,
fetcher?: fetcherFn<D>,
config?: Omit<ConfigInterface<D, E, fetcherFn<D>>, 'dedupingInterval' | 'errorRetryInterval'>
): Response<D, E>;
function useRunningRequest<D = unknown, E = unknown>(
function useRunningRequest<D = unknown, E extends Error = Error>(
key: keyInterface,
running: boolean,
fetcher?: fetcherFn<D>,
Expand Down
Loading