Explorar o código

ErrorHandling: Error boundary for every container (#18845)

* ErrorHandling: Error boundary for every container

* Remvoe custom query editor errors
Torkel Ödegaard %!s(int64=6) %!d(string=hai) anos
pai
achega
490a1d6fc9

+ 40 - 15
public/app/core/components/ErrorBoundary/ErrorBoundary.tsx → packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx

@@ -1,13 +1,14 @@
 import React, { PureComponent, ReactNode } from 'react';
-import { Alert } from '@grafana/ui';
+import { Alert } from '../Alert/Alert';
+import { css } from 'emotion';
 
 interface ErrorInfo {
   componentStack: string;
 }
 
 interface RenderProps {
-  error: Error;
-  errorInfo: ErrorInfo;
+  error: Error | null;
+  errorInfo: ErrorInfo | null;
 }
 
 interface Props {
@@ -15,8 +16,8 @@ interface Props {
 }
 
 interface State {
-  error: Error;
-  errorInfo: ErrorInfo;
+  error: Error | null;
+  errorInfo: ErrorInfo | null;
 }
 
 export class ErrorBoundary extends PureComponent<Props, State> {
@@ -35,6 +36,7 @@ export class ErrorBoundary extends PureComponent<Props, State> {
   render() {
     const { children } = this.props;
     const { error, errorInfo } = this.state;
+
     return children({
       error,
       errorInfo,
@@ -42,18 +44,28 @@ export class ErrorBoundary extends PureComponent<Props, State> {
   }
 }
 
+function getAlertPageStyle() {
+  return css`
+    width: 500px;
+    margin: 64px auto;
+  `;
+}
+
 interface WithAlertBoxProps {
   title?: string;
   children: ReactNode;
+  style?: 'page' | 'alertbox';
 }
 
 export class ErrorBoundaryAlert extends PureComponent<WithAlertBoxProps> {
   static defaultProps: Partial<WithAlertBoxProps> = {
     title: 'An unexpected error happened',
+    style: 'alertbox',
   };
 
   render() {
-    const { title, children } = this.props;
+    const { title, children, style } = this.props;
+
     return (
       <ErrorBoundary>
         {({ error, errorInfo }) => {
@@ -61,15 +73,28 @@ export class ErrorBoundaryAlert extends PureComponent<WithAlertBoxProps> {
             return children;
           }
 
-          return (
-            <Alert title={title}>
-              <details style={{ whiteSpace: 'pre-wrap' }}>
-                {error && error.toString()}
-                <br />
-                {errorInfo.componentStack}
-              </details>
-            </Alert>
-          );
+          if (style === 'alertbox') {
+            return (
+              <Alert title={title || ''}>
+                <details style={{ whiteSpace: 'pre-wrap' }}>
+                  {error && error.toString()}
+                  <br />
+                  {errorInfo.componentStack}
+                </details>
+              </Alert>
+            );
+          } else {
+            return (
+              <div className={getAlertPageStyle()}>
+                <h2>{title}</h2>
+                <details style={{ whiteSpace: 'pre-wrap' }}>
+                  {error && error.toString()}
+                  <br />
+                  {errorInfo.componentStack}
+                </details>
+              </div>
+            );
+          }
         }}
       </ErrorBoundary>
     );

+ 2 - 0
packages/grafana-ui/src/components/index.ts

@@ -78,3 +78,5 @@ export { VariableSuggestion, VariableOrigin } from './DataLinks/DataLinkSuggesti
 export { DataLinksEditor } from './DataLinks/DataLinksEditor';
 export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
 export { SeriesIcon } from './Legend/SeriesIcon';
+
+export { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary/ErrorBoundary';

+ 1 - 1
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -5,7 +5,7 @@ import { Unsubscribable } from 'rxjs';
 
 // Components
 import { PanelHeader } from './PanelHeader/PanelHeader';
-import { ErrorBoundary } from 'app/core/components/ErrorBoundary/ErrorBoundary';
+import { ErrorBoundary } from '@grafana/ui';
 
 // Utils & Services
 import { getTimeSrv, TimeSrv } from '../services/TimeSrv';

+ 2 - 3
public/app/features/dashboard/panel_editor/QueryEditorRow.tsx

@@ -8,11 +8,10 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
 import { AngularComponent, getAngularLoader } from '@grafana/runtime';
 import { Emitter } from 'app/core/utils/emitter';
 import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
-import { ErrorBoundaryAlert } from 'app/core/components/ErrorBoundary/ErrorBoundary';
 
 // Types
 import { PanelModel } from '../state/PanelModel';
-import { DataQuery, DataSourceApi, PanelData, DataQueryRequest } from '@grafana/ui';
+import { DataQuery, DataSourceApi, PanelData, DataQueryRequest, ErrorBoundaryAlert } from '@grafana/ui';
 import { TimeRange, LoadingState } from '@grafana/data';
 import { DashboardModel } from '../state/DashboardModel';
 
@@ -259,7 +258,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
           </div>
         </div>
         <div className={bodyClasses}>
-          <ErrorBoundaryAlert title="Data source query editor failed">{this.renderPluginEditor()}</ErrorBoundaryAlert>
+          <ErrorBoundaryAlert>{this.renderPluginEditor()}</ErrorBoundaryAlert>
         </div>
       </div>
     );

+ 0 - 34
public/app/features/explore/ErrorBoundary.tsx

@@ -1,34 +0,0 @@
-import React, { Component } from 'react';
-
-export class ErrorBoundary extends Component<{}, any> {
-  constructor(props: {}) {
-    super(props);
-    this.state = { error: null, errorInfo: null };
-  }
-
-  componentDidCatch(error: any, errorInfo: any) {
-    // Catch errors in any components below and re-render with error message
-    this.setState({
-      error,
-      errorInfo,
-    });
-  }
-
-  render() {
-    if (this.state.errorInfo) {
-      // Error path
-      return (
-        <div className="explore-container">
-          <h3>An unexpected error happened.</h3>
-          <details style={{ whiteSpace: 'pre-wrap' }}>
-            {this.state.error && this.state.error.toString()}
-            <br />
-            {this.state.errorInfo.componentStack}
-          </details>
-        </div>
-      );
-    }
-    // Normally, just render children
-    return this.props.children;
-  }
-}

+ 3 - 4
public/app/features/explore/Explore.tsx

@@ -9,8 +9,7 @@ import memoizeOne from 'memoize-one';
 // Services & Utils
 import store from 'app/core/store';
 // Components
-import { Alert, DataQuery, ExploreStartPageProps, DataSourceApi, PanelData } from '@grafana/ui';
-import { ErrorBoundary } from './ErrorBoundary';
+import { Alert, ErrorBoundaryAlert, DataQuery, ExploreStartPageProps, DataSourceApi, PanelData } from '@grafana/ui';
 import LogsContainer from './LogsContainer';
 import QueryRows from './QueryRows';
 import TableContainer from './TableContainer';
@@ -275,7 +274,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
 
                 return (
                   <main className="m-t-2" style={{ width }}>
-                    <ErrorBoundary>
+                    <ErrorBoundaryAlert>
                       {showingStartPage && <StartPage onClickExample={this.onClickExample} />}
                       {!showingStartPage && (
                         <>
@@ -310,7 +309,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
                           )}
                         </>
                       )}
-                    </ErrorBoundary>
+                    </ErrorBoundaryAlert>
                   </main>
                 );
               }}

+ 5 - 6
public/app/features/explore/Wrapper.tsx

@@ -5,9 +5,8 @@ import { connect } from 'react-redux';
 import { StoreState } from 'app/types';
 import { ExploreId } from 'app/types/explore';
 
-import { ErrorBoundary } from './ErrorBoundary';
 import Explore from './Explore';
-import { CustomScrollbar } from '@grafana/ui';
+import { CustomScrollbar, ErrorBoundaryAlert } from '@grafana/ui';
 import { resetExploreAction } from './state/actionTypes';
 
 interface WrapperProps {
@@ -27,13 +26,13 @@ export class Wrapper extends Component<WrapperProps> {
       <div className="page-scrollbar-wrapper">
         <CustomScrollbar autoHeightMin={'100%'} autoHeightMax={''} className="custom-scrollbar--page">
           <div className="explore-wrapper">
-            <ErrorBoundary>
+            <ErrorBoundaryAlert style="page">
               <Explore exploreId={ExploreId.left} />
-            </ErrorBoundary>
+            </ErrorBoundaryAlert>
             {split && (
-              <ErrorBoundary>
+              <ErrorBoundaryAlert style="page">
                 <Explore exploreId={ExploreId.right} />
-              </ErrorBoundary>
+              </ErrorBoundaryAlert>
             )}
           </div>
         </CustomScrollbar>

+ 4 - 1
public/app/routes/ReactContainer.tsx

@@ -8,11 +8,14 @@ import coreModule from 'app/core/core_module';
 import { store } from 'app/store/store';
 import { ContextSrv } from 'app/core/services/context_srv';
 import { provideTheme } from 'app/core/utils/ConfigProvider';
+import { ErrorBoundaryAlert } from '@grafana/ui';
 
 function WrapInProvider(store: any, Component: any, props: any) {
   return (
     <Provider store={store}>
-      <Component {...props} />
+      <ErrorBoundaryAlert style="page">
+        <Component {...props} />
+      </ErrorBoundaryAlert>
     </Provider>
   );
 }