React UI: Better exception handling (#1297)
parent
9ef89c724f
commit
796044782f
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionUnion,
|
||||||
|
createAction,
|
||||||
|
ThunkAction,
|
||||||
|
ThunkDispatch,
|
||||||
|
} from 'utils/redux';
|
||||||
|
import getCore from 'cvat-core';
|
||||||
|
import { LogType } from 'cvat-logger';
|
||||||
|
import { computeZRange } from './annotation-actions';
|
||||||
|
|
||||||
|
const cvat = getCore();
|
||||||
|
|
||||||
|
export enum BoundariesActionTypes {
|
||||||
|
RESET_AFTER_ERROR = 'RESET_AFTER_ERROR',
|
||||||
|
THROW_RESET_ERROR = 'THROW_RESET_ERROR',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const boundariesActions = {
|
||||||
|
resetAfterError: (
|
||||||
|
job: any,
|
||||||
|
states: any[],
|
||||||
|
frameNumber: number,
|
||||||
|
frameData: any | null,
|
||||||
|
minZ: number,
|
||||||
|
maxZ: number,
|
||||||
|
colors: string[],
|
||||||
|
) => createAction(BoundariesActionTypes.RESET_AFTER_ERROR, {
|
||||||
|
job,
|
||||||
|
states,
|
||||||
|
frameNumber,
|
||||||
|
frameData,
|
||||||
|
minZ,
|
||||||
|
maxZ,
|
||||||
|
colors,
|
||||||
|
}),
|
||||||
|
throwResetError: () => createAction(BoundariesActionTypes.THROW_RESET_ERROR),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resetAfterErrorAsync(): ThunkAction {
|
||||||
|
return async (dispatch: ThunkDispatch, getState): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const state = getState();
|
||||||
|
const job = state.annotation.job.instance;
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
const currentFrame = state.annotation.player.frame.number;
|
||||||
|
const { showAllInterpolationTracks } = state.settings.workspace;
|
||||||
|
const frameNumber = Math.max(Math.min(job.stopFrame, currentFrame), job.startFrame);
|
||||||
|
|
||||||
|
const states = await job.annotations
|
||||||
|
.get(frameNumber, showAllInterpolationTracks, []);
|
||||||
|
const frameData = await job.frames.get(frameNumber);
|
||||||
|
const [minZ, maxZ] = computeZRange(states);
|
||||||
|
const colors = [...cvat.enums.colors];
|
||||||
|
|
||||||
|
await job.logger.log(LogType.restoreJob);
|
||||||
|
|
||||||
|
dispatch(boundariesActions.resetAfterError(
|
||||||
|
job,
|
||||||
|
states,
|
||||||
|
frameNumber,
|
||||||
|
frameData,
|
||||||
|
minZ,
|
||||||
|
maxZ,
|
||||||
|
colors,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
dispatch(boundariesActions.resetAfterError(
|
||||||
|
null,
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
[],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(boundariesActions.throwResetError());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type boundariesActions = ActionUnion<typeof boundariesActions>;
|
||||||
@ -0,0 +1,223 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Action } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import Result from 'antd/lib/result';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||||
|
import Collapse from 'antd/lib/collapse';
|
||||||
|
import TextArea from 'antd/lib/input/TextArea';
|
||||||
|
import Tooltip from 'antd/lib/tooltip';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import ErrorStackParser from 'error-stack-parser';
|
||||||
|
|
||||||
|
import { resetAfterErrorAsync } from 'actions/boundaries-actions';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import logger, { LogType } from 'cvat-logger';
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
job: any | null;
|
||||||
|
serverVersion: string;
|
||||||
|
coreVersion: string;
|
||||||
|
canvasVersion: string;
|
||||||
|
uiVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
restore(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
hasError: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
job: {
|
||||||
|
instance: job,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
about: {
|
||||||
|
server,
|
||||||
|
packageVersion,
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
job,
|
||||||
|
serverVersion: server.version as string,
|
||||||
|
coreVersion: packageVersion.core,
|
||||||
|
canvasVersion: packageVersion.canvas,
|
||||||
|
uiVersion: packageVersion.ui,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps {
|
||||||
|
return {
|
||||||
|
restore(): void {
|
||||||
|
dispatch(resetAfterErrorAsync());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps;
|
||||||
|
class GlobalErrorBoundary extends React.PureComponent<Props, State> {
|
||||||
|
public constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
hasError: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): State {
|
||||||
|
return {
|
||||||
|
hasError: true,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
||||||
|
const { job } = this.props;
|
||||||
|
const parsed = ErrorStackParser.parse(error);
|
||||||
|
|
||||||
|
const logPayload = {
|
||||||
|
filename: parsed[0].fileName,
|
||||||
|
line: parsed[0].lineNumber,
|
||||||
|
message: error.message,
|
||||||
|
column: parsed[0].columnNumber,
|
||||||
|
stack: error.stack,
|
||||||
|
componentStack: errorInfo.componentStack,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (job) {
|
||||||
|
job.logger.log(LogType.sendException, logPayload);
|
||||||
|
} else {
|
||||||
|
logger.log(LogType.sendException, logPayload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): React.ReactNode {
|
||||||
|
const {
|
||||||
|
restore,
|
||||||
|
job,
|
||||||
|
serverVersion,
|
||||||
|
coreVersion,
|
||||||
|
canvasVersion,
|
||||||
|
uiVersion,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { hasError, error } = this.state;
|
||||||
|
|
||||||
|
const restoreGlobalState = (): void => {
|
||||||
|
this.setState({
|
||||||
|
error: null,
|
||||||
|
hasError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasError && error) {
|
||||||
|
const message = `${error.name}\n${error.message}\n\n${error.stack}`;
|
||||||
|
return (
|
||||||
|
<div className='cvat-global-boundary'>
|
||||||
|
<Result
|
||||||
|
status='error'
|
||||||
|
title='Oops, something went wrong'
|
||||||
|
subTitle='More likely there are some issues with the tool'
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Paragraph>
|
||||||
|
<Paragraph strong>What has happened?</Paragraph>
|
||||||
|
<Paragraph>Program error has just occured</Paragraph>
|
||||||
|
<Collapse accordion>
|
||||||
|
<Collapse.Panel header='Error message' key='errorMessage'>
|
||||||
|
<Text type='danger'>
|
||||||
|
<TextArea className='cvat-global-boundary-error-field' autoSize value={message} />
|
||||||
|
</Text>
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
</Paragraph>
|
||||||
|
|
||||||
|
<Paragraph>
|
||||||
|
<Text strong>What should I do?</Text>
|
||||||
|
</Paragraph>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Tooltip title='Copied!' trigger='click'>
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
|
<a onClick={() => {copy(message)}}> Copy </a>
|
||||||
|
</Tooltip>
|
||||||
|
the error message to clipboard
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Notify an administrator or submit the issue directly on
|
||||||
|
<a href='https://github.com/opencv/cvat'> GitHub. </a>
|
||||||
|
Please, provide also:
|
||||||
|
<ul>
|
||||||
|
<li>Steps to reproduce the issue</li>
|
||||||
|
<li>Your operating system and browser version</li>
|
||||||
|
<li>CVAT version</li>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Text strong>Server: </Text>
|
||||||
|
{serverVersion}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Text strong>Core: </Text>
|
||||||
|
{coreVersion}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Text strong>Canvas: </Text>
|
||||||
|
{canvasVersion}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Text strong>UI: </Text>
|
||||||
|
{uiVersion}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{job ? (
|
||||||
|
<li>
|
||||||
|
Press
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
|
<a onClick={restoreGlobalState}> here </a>
|
||||||
|
if you wish CVAT tried to restore your
|
||||||
|
annotation progress or
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
|
<a onClick={() => window.location.reload()}> update </a>
|
||||||
|
the page
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
<li>
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
|
<a onClick={() => window.location.reload()}>Update </a>
|
||||||
|
the page
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Result>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children } = this.props;
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(GlobalErrorBoundary);
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (C) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-global-boundary {
|
||||||
|
.ant-result > .ant-result-content {
|
||||||
|
background-color: $transparent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-global-boundary-error-field {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue