未验证 提交 79604478 编写于 作者: B Boris Sekachev 提交者: GitHub

React UI: Better exception handling (#1297)

上级 9ef89c72
......@@ -34,6 +34,7 @@
"dependencies": {
"axios": "^0.18.0",
"browser-or-node": "^1.2.1",
"detect-browser": "^5.0.0",
"error-stack-parser": "^2.0.2",
"form-data": "^2.5.0",
"jest-config": "^24.8.0",
......
......@@ -109,6 +109,7 @@
* @memberof module:API.cvat.enums
* @property {string} loadJob Load job
* @property {string} saveJob Save job
* @property {string} restoreJob Restore job
* @property {string} uploadAnnotations Upload annotations
* @property {string} sendUserActivity Send user activity
* @property {string} sendException Send exception
......@@ -142,6 +143,7 @@
const LogType = Object.freeze({
loadJob: 'Load job',
saveJob: 'Save job',
restoreJob: 'Restore job',
uploadAnnotations: 'Upload annotations',
sendUserActivity: 'Send user activity',
sendException: 'Send exception',
......
......@@ -6,6 +6,7 @@
require:false
*/
const { detect } = require('detect-browser');
const PluginRegistry = require('./plugins');
const { ArgumentError } = require('./exceptions');
const { LogType } = require('./enums');
......@@ -179,6 +180,48 @@ class LogWithExceptionInfo extends Log {
+ 'It must be a number';
throw new ArgumentError(message);
}
if (typeof (this.payload.column) !== 'number') {
const message = `The field "column" is required for ${this.type} log. `
+ 'It must be a number';
throw new ArgumentError(message);
}
if (typeof (this.payload.stack) !== 'string') {
const message = `The field "stack" is required for ${this.type} log. `
+ 'It must be a string';
throw new ArgumentError(message);
}
}
dump() {
const payload = { ...this.payload };
const client = detect();
const body = {
client_id: payload.client_id,
name: this.type,
time: this.time.toISOString(),
message: payload.message,
filename: payload.filename,
line: payload.line,
column: payload.column,
stack: payload.stack,
system: client.os,
client: client.name,
version: client.version,
};
delete payload.client_id;
delete payload.message;
delete payload.filename;
delete payload.line;
delete payload.column;
delete payload.stack;
return {
...body,
payload,
};
}
}
......
......@@ -7,7 +7,7 @@
*/
const PluginRegistry = require('./plugins');
const server = require('./server-proxy');
const serverProxy = require('./server-proxy');
const logFactory = require('./log');
const { ArgumentError } = require('./exceptions');
const { LogType } = require('./enums');
......@@ -128,6 +128,14 @@ LoggerStorage.prototype.log.implementation = function (logType, payload, wait) {
this.collection.push(log);
};
if (log.type === LogType.sendException) {
serverProxy.server.exception(log.dump()).catch(() => {
pushEvent();
});
return log;
}
if (wait) {
log.onClose(pushEvent);
} else {
......@@ -156,7 +164,7 @@ LoggerStorage.prototype.save.implementation = async function () {
const userActivityLog = logFactory(LogType.sendUserActivity, logPayload);
collectionToSend.push(userActivityLog);
await server.logs.save(collectionToSend.map((log) => log.dump()));
await serverProxy.logs.save(collectionToSend.map((log) => log.dump()));
for (const rule of Object.values(this.ignoreRules)) {
rule.lastLog = null;
......
......@@ -3815,6 +3815,14 @@
"is-arrayish": "^0.2.1"
}
},
"error-stack-parser": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
"requires": {
"stackframe": "^1.1.1"
}
},
"es-abstract": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz",
......@@ -11203,6 +11211,11 @@
"integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
"dev": true
},
"stackframe": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz",
"integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ=="
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
......@@ -11981,9 +11994,9 @@
"dev": true
},
"ua-parser-js": {
"version": "0.7.20",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz",
"integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw=="
"version": "0.7.21",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz",
"integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ=="
},
"uglify-js": {
"version": "3.4.10",
......
......@@ -57,6 +57,7 @@
"antd": "^3.25.2",
"copy-to-clipboard": "^3.2.0",
"dotenv-webpack": "^1.7.0",
"error-stack-parser": "^2.0.6",
"moment": "^2.24.0",
"prop-types": "^15.7.2",
"react": "^16.9.0",
......
......@@ -78,10 +78,14 @@ function receiveAnnotationsParameters(): AnnotationsParameters {
};
}
function computeZRange(states: any[]): number[] {
export function computeZRange(states: any[]): number[] {
let minZ = states.length ? states[0].zOrder : 0;
let maxZ = states.length ? states[0].zOrder : 0;
states.forEach((state: any): void => {
if (state.objectType === ObjectType.TAG) {
return;
}
minZ = Math.min(minZ, state.zOrder);
maxZ = Math.max(maxZ, state.zOrder);
});
......
// 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>;
......@@ -5,6 +5,8 @@
import React, { useState, useEffect } from 'react';
import { GlobalHotKeys, KeyMap } from 'react-hotkeys';
import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import Layout, { SiderProps } from 'antd/lib/layout';
import { SelectValue } from 'antd/lib/select';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
......@@ -65,7 +67,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
};
}
function mapDispatchToProps(dispatch: any): DispatchToProps {
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps {
return {
activateObject(clientID: number, attrID: number): void {
dispatch(activateObjectAction(clientID, attrID));
......
......@@ -8,13 +8,11 @@ import React from 'react';
import { Switch, Route, Redirect } from 'react-router';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { GlobalHotKeys, KeyMap, configure } from 'react-hotkeys';
import Spin from 'antd/lib/spin';
import Layout from 'antd/lib/layout';
import notification from 'antd/lib/notification';
import {
Spin,
Layout,
notification,
} from 'antd';
import GlobalErrorBoundary from 'components/global-error-boundary/global-error-boundary';
import ShorcutsDialog from 'components/shortcuts-dialog/shortcuts-dialog';
import SettingsPageContainer from 'containers/settings-page/settings-page';
import TasksPageContainer from 'containers/tasks-page/tasks-page';
......@@ -40,6 +38,7 @@ interface CVATAppProps {
resetMessages: () => void;
switchShortcutsDialog: () => void;
userInitialized: boolean;
userFetching: boolean;
pluginsInitialized: boolean;
pluginsFetching: boolean;
formatsInitialized: boolean;
......@@ -73,11 +72,13 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
public componentDidUpdate(): void {
const {
verifyAuthorized,
loadFormats,
loadUsers,
loadAbout,
initPlugins,
userInitialized,
userFetching,
formatsInitialized,
formatsFetching,
usersInitialized,
......@@ -92,8 +93,12 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
this.showErrors();
this.showMessages();
if (!userInitialized || user == null) {
// not authorized user
if (!userInitialized && !userFetching) {
verifyAuthorized();
return;
}
if (user == null) {
return;
}
......@@ -252,37 +257,41 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
if (readyForRender) {
if (user) {
return (
<Layout>
<HeaderContainer> </HeaderContainer>
<Layout.Content>
<ShorcutsDialog />
<GlobalHotKeys keyMap={keyMap as KeyMap} handlers={handlers}>
<Switch>
<Route exact path='/settings' component={SettingsPageContainer} />
<Route exact path='/tasks' component={TasksPageContainer} />
<Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} />
<Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
{withModels
&& <Route exact path='/models' component={ModelsPageContainer} />}
{installedAutoAnnotation
&& <Route exact path='/models/create' component={CreateModelPageContainer} />}
<Redirect push to='/tasks' />
</Switch>
</GlobalHotKeys>
{/* eslint-disable-next-line */}
<a id='downloadAnchor' style={{ display: 'none' }} download />
</Layout.Content>
</Layout>
<GlobalErrorBoundary>
<Layout>
<HeaderContainer> </HeaderContainer>
<Layout.Content>
<ShorcutsDialog />
<GlobalHotKeys keyMap={keyMap as KeyMap} handlers={handlers}>
<Switch>
<Route exact path='/settings' component={SettingsPageContainer} />
<Route exact path='/tasks' component={TasksPageContainer} />
<Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} />
<Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
{withModels
&& <Route exact path='/models' component={ModelsPageContainer} />}
{installedAutoAnnotation
&& <Route exact path='/models/create' component={CreateModelPageContainer} />}
<Redirect push to='/tasks' />
</Switch>
</GlobalHotKeys>
{/* eslint-disable-next-line */}
<a id='downloadAnchor' style={{ display: 'none' }} download />
</Layout.Content>
</Layout>
</GlobalErrorBoundary>
);
}
return (
<Switch>
<Route exact path='/auth/register' component={RegisterPageContainer} />
<Route exact path='/auth/login' component={LoginPageContainer} />
<Redirect to='/auth/login' />
</Switch>
<GlobalErrorBoundary>
<Switch>
<Route exact path='/auth/register' component={RegisterPageContainer} />
<Route exact path='/auth/login' component={LoginPageContainer} />
<Redirect to='/auth/login' />
</Switch>
</GlobalErrorBoundary>
);
}
......
// 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);
// 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;
}
}
......@@ -7,17 +7,18 @@ import ReactDOM from 'react-dom';
import { connect, Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import CVATApplication from './components/cvat-app';
import CVATApplication from 'components/cvat-app';
import createRootReducer from './reducers/root-reducer';
import createCVATStore, { getCVATStore } from './cvat-store';
import createRootReducer from 'reducers/root-reducer';
import createCVATStore, { getCVATStore } from 'cvat-store';
import logger, { LogType } from 'cvat-logger';
import { authorizedAsync } from './actions/auth-actions';
import { getFormatsAsync } from './actions/formats-actions';
import { checkPluginsAsync } from './actions/plugins-actions';
import { getUsersAsync } from './actions/users-actions';
import { getAboutAsync } from './actions/about-actions';
import { shortcutsActions } from './actions/shortcuts-actions';
import { authorizedAsync } from 'actions/auth-actions';
import { getFormatsAsync } from 'actions/formats-actions';
import { checkPluginsAsync } from 'actions/plugins-actions';
import { getUsersAsync } from 'actions/users-actions';
import { getAboutAsync } from 'actions/about-actions';
import { shortcutsActions } from 'actions/shortcuts-actions';
import {
resetErrors,
resetMessages,
......@@ -35,6 +36,7 @@ interface StateToProps {
pluginsInitialized: boolean;
pluginsFetching: boolean;
userInitialized: boolean;
userFetching: boolean;
usersInitialized: boolean;
usersFetching: boolean;
aboutInitialized: boolean;
......@@ -68,6 +70,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
return {
userInitialized: auth.initialized,
userFetching: auth.fetching,
pluginsInitialized: plugins.initialized,
pluginsFetching: plugins.fetching,
usersInitialized: users.initialized,
......@@ -112,3 +115,32 @@ ReactDOM.render(
),
document.getElementById('root'),
);
window.onerror = (
message: Event | string,
source?: string,
lineno?: number,
colno?: number,
error?: Error,
) => {
if (typeof (message) === 'string' && source && typeof (lineno) === 'number' && (typeof (colno) === 'number') && error) {
const logPayload = {
filename: source,
line: lineno,
message: error.message,
column: colno,
stack: error.stack,
};
const store = getCVATStore();
const state: CombinedState = store.getState();
const { pathname } = window.location;
const re = RegExp(/\/tasks\/[0-9]+\/jobs\/[0-9]+$/);
const { instance: job } = state.annotation.job;
if (re.test(pathname) && job) {
job.logger.log(LogType.sendException, logPayload);
} else {
logger.log(LogType.sendException, logPayload);
}
}
};
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { AboutActions, AboutActionTypes } from 'actions/about-actions';
import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
import { AboutState } from './interfaces';
......@@ -23,7 +24,7 @@ const defaultState: AboutState = {
export default function (
state: AboutState = defaultState,
action: AboutActions | AuthActions,
action: AboutActions | AuthActions | boundariesActions,
): AboutState {
switch (action.type) {
case AboutActionTypes.GET_ABOUT: {
......@@ -46,7 +47,8 @@ export default function (
fetching: false,
initialized: true,
};
case AuthActionTypes.LOGOUT_SUCCESS: {
case AuthActionTypes.LOGOUT_SUCCESS:
case BoundariesActionTypes.RESET_AFTER_ERROR: {
return {
...defaultState,
};
......
......@@ -7,6 +7,7 @@ import { AnyAction } from 'redux';
import { Canvas, CanvasMode } from 'cvat-canvas';
import { AnnotationActionTypes } from 'actions/annotation-actions';
import { AuthActionTypes } from 'actions/auth-actions';
import { BoundariesActionTypes } from 'actions/boundaries-actions';
import {
AnnotationState,
ActiveControl,
......@@ -104,6 +105,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AnnotationActionTypes.GET_JOB_SUCCESS: {
const {
job,
......@@ -153,6 +155,10 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
activeLabelID: job.task.labels[0].id,
activeObjectType: job.task.mode === 'interpolation' ? ObjectType.TRACK : ObjectType.SHAPE,
},
canvas: {
...state.canvas,
instance: new Canvas(),
},
colors,
};
}
......@@ -166,15 +172,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case AnnotationActionTypes.CLOSE_JOB: {
return {
...defaultState,
canvas: {
...defaultState.canvas,
instance: new Canvas(),
},
};
}
case AnnotationActionTypes.CHANGE_FRAME: {
return {
...state,
......@@ -1065,10 +1062,9 @@ export default (state = defaultState, action: AnyAction): AnnotationState => {
},
};
}
case AnnotationActionTypes.CLOSE_JOB:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default: {
return state;
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
import { AuthState } from './interfaces';
......@@ -11,7 +12,7 @@ const defaultState: AuthState = {
user: null,
};
export default function (state = defaultState, action: AuthActions): AuthState {
export default function (state = defaultState, action: AuthActions | boundariesActions): AuthState {
switch (action.type) {
case AuthActionTypes.AUTHORIZED_SUCCESS:
return {
......@@ -68,6 +69,9 @@ export default function (state = defaultState, action: AuthActions): AuthState {
...state,
fetching: false,
};
case BoundariesActionTypes.RESET_AFTER_ERROR: {
return { ...defaultState };
}
default:
return state;
}
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { FormatsActionTypes, FormatsActions } from 'actions/formats-actions';
import { AuthActionTypes, AuthActions } from 'actions/auth-actions';
......@@ -16,7 +17,7 @@ const defaultState: FormatsState = {
export default (
state: FormatsState = defaultState,
action: FormatsActions | AuthActions,
action: FormatsActions | AuthActions | boundariesActions,
): FormatsState => {
switch (action.type) {
case FormatsActionTypes.GET_FORMATS: {
......@@ -40,10 +41,9 @@ export default (
initialized: true,
fetching: false,
};
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default:
return state;
......
......@@ -232,6 +232,9 @@ export interface NotificationsState {
search: null | ErrorState;
savingLogs: null | ErrorState;
};
boundaries: {
resetError: null | ErrorState;
};
[index: string]: any;
};
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { ModelsActionTypes, ModelsActions } from 'actions/models-actions';
import { AuthActionTypes, AuthActions } from 'actions/auth-actions';
import { ModelsState } from './interfaces';
......@@ -16,7 +17,10 @@ const defaultState: ModelsState = {
inferences: {},
};
export default function (state = defaultState, action: ModelsActions | AuthActions): ModelsState {
export default function (
state = defaultState,
action: ModelsActions | AuthActions | boundariesActions,
): ModelsState {
switch (action.type) {
case ModelsActionTypes.GET_MODELS: {
return {
......@@ -118,10 +122,9 @@ export default function (state = defaultState, action: ModelsActions | AuthActio
inferences: { ...inferences },
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default: {
return state;
......
......@@ -13,6 +13,7 @@ import { UsersActionTypes } from 'actions/users-actions';
import { AboutActionTypes } from 'actions/about-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions';
import { NotificationsActionType } from 'actions/notification-actions';
import { BoundariesActionTypes } from 'actions/boundaries-actions';
import { NotificationsState } from './interfaces';
......@@ -76,6 +77,9 @@ const defaultState: NotificationsState = {
search: null,
savingLogs: null,
},
boundaries: {
resetError: null,
},
},
messages: {
tasks: {
......@@ -782,6 +786,21 @@ export default function (state = defaultState, action: AnyAction): Notifications
},
};
}
case BoundariesActionTypes.THROW_RESET_ERROR: {
return {
...state,
errors: {
...state.errors,
boundaries: {
...state.errors.annotation,
resetError: {
message: 'Could not reset the state',
reason: action.payload.error.toString(),
},
},
},
};
}
case NotificationsActionType.RESET_ERRORS: {
return {
...state,
......@@ -798,10 +817,9 @@ export default function (state = defaultState, action: AnyAction): Notifications
},
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default: {
return state;
......
......@@ -3,6 +3,9 @@
// SPDX-License-Identifier: MIT
import { AnyAction } from 'redux';
import { BoundariesActionTypes } from 'actions/boundaries-actions';
import { AuthActionTypes } from 'actions/auth-actions';
import { SettingsActionTypes } from 'actions/settings-actions';
import { AnnotationActionTypes } from 'actions/annotation-actions';
......@@ -225,6 +228,10 @@ export default (state = defaultState, action: AnyAction): SettingsState => {
},
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return { ...defaultState };
}
default: {
return state;
}
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { BoundariesActionTypes, boundariesActions } from 'actions/boundaries-actions';
import { ShareActionTypes, ShareActions } from 'actions/share-actions';
import { AuthActionTypes, AuthActions } from 'actions/auth-actions';
import {
......@@ -20,7 +21,7 @@ const defaultState: ShareState = {
export default function (
state: ShareState = defaultState,
action: ShareActions | AuthActions,
action: ShareActions | AuthActions | boundariesActions,
): ShareState {
switch (action.type) {
case ShareActionTypes.LOAD_SHARE_DATA_SUCCESS: {
......@@ -48,10 +49,9 @@ export default function (
...state,
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default:
return state;
......
import { boundariesActions, BoundariesActionTypes } from 'actions/boundaries-actions';
import { AuthActions, AuthActionTypes } from 'actions/auth-actions';
import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions';
import { ShortcutsState } from './interfaces';
......@@ -5,7 +7,10 @@ const defaultState: ShortcutsState = {
visibleShortcutsHelp: false,
};
export default (state = defaultState, action: ShortcutsActions): ShortcutsState => {
export default (
state = defaultState,
action: ShortcutsActions | boundariesActions | AuthActions,
): ShortcutsState => {
switch (action.type) {
case ShortcutsActionsTypes.SWITCH_SHORTCUT_DIALOG: {
return {
......@@ -13,6 +18,10 @@ export default (state = defaultState, action: ShortcutsActions): ShortcutsState
visibleShortcutsHelp: !state.visibleShortcutsHelp,
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return { ...defaultState };
}
default: {
return state;
}
......
......@@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
import { AnyAction } from 'redux';
import { BoundariesActionTypes } from 'actions/boundaries-actions';
import { TasksActionTypes } from 'actions/tasks-actions';
import { AuthActionTypes } from 'actions/auth-actions';
......@@ -320,10 +321,9 @@ export default (state: TasksState = defaultState, action: AnyAction): TasksState
hideEmpty: action.payload.hideEmpty,
};
}
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default:
return state;
......
......@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import { BoundariesActionTypes, boundariesActions } from 'actions/boundaries-actions';
import { AuthActionTypes, AuthActions } from 'actions/auth-actions';
import { UsersActionTypes, UsersActions } from 'actions/users-actions';
import { UsersState } from './interfaces';
......@@ -14,7 +15,7 @@ const defaultState: UsersState = {
export default function (
state: UsersState = defaultState,
action: UsersActions | AuthActions,
action: UsersActions | AuthActions | boundariesActions,
): UsersState {
switch (action.type) {
case UsersActionTypes.GET_USERS: {
......@@ -37,10 +38,9 @@ export default function (
fetching: false,
initialized: true,
};
case BoundariesActionTypes.RESET_AFTER_ERROR:
case AuthActionTypes.LOGOUT_SUCCESS: {
return {
...defaultState,
};
return { ...defaultState };
}
default:
return state;
......
......@@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import { Action, ActionCreatorsMapObject, AnyAction } from 'redux';
import { ThunkAction as _ThunkAction } from 'redux-thunk';
import { ThunkAction as _ThunkAction, ThunkDispatch as _ThunkDispatch } from 'redux-thunk';
import { CombinedState } from '../reducers/interfaces';
export interface ActionWithPayload<T, P> extends Action<T> {
......@@ -22,3 +22,6 @@ export type ActionUnion<A extends ActionCreatorsMapObject> = ReturnType<A[keyof
export type ThunkAction<R = void, A extends Action = AnyAction>
= _ThunkAction<R, CombinedState, {}, A>;
export type ThunkDispatch<E = void, A extends Action = AnyAction>
= _ThunkDispatch<CombinedState, E, A>;
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册