You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by gr...@apache.org on 2020/03/26 16:06:39 UTC

[incubator-superset] branch master updated: [dashboard] handle markdown error (#9350)

This is an automated email from the ASF dual-hosted git repository.

graceguo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b07c8d  [dashboard] handle markdown error (#9350)
5b07c8d is described below

commit 5b07c8d22901b7b173bd90f185904ea5c28bdad6
Author: Grace Guo <gr...@airbnb.com>
AuthorDate: Thu Mar 26 09:06:23 2020 -0700

    [dashboard] handle markdown error (#9350)
    
    * [dashboard] handle markdown error
    
    * localize error message, fix review comments.
---
 .../components/gridComponents/Markdown.jsx         | 84 +++++++++++++++++++---
 .../dashboard/containers/DashboardComponent.jsx    |  4 ++
 2 files changed, 77 insertions(+), 11 deletions(-)

diff --git a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
index 59137ba..31415dc 100644
--- a/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
+++ b/superset-frontend/src/dashboard/components/gridComponents/Markdown.jsx
@@ -23,6 +23,7 @@ import cx from 'classnames';
 import AceEditor from 'react-ace';
 import 'brace/mode/markdown';
 import 'brace/theme/textmate';
+import { t } from '@superset-ui/translation';
 
 import DeleteComponentButton from '../DeleteComponentButton';
 import DragDroppable from '../dnd/DragDroppable';
@@ -49,6 +50,9 @@ const propTypes = {
 
   // from redux
   logEvent: PropTypes.func.isRequired,
+  addDangerToast: PropTypes.func.isRequired,
+  undoLength: PropTypes.number.isRequired,
+  redoLength: PropTypes.number.isRequired,
 
   // grid related
   availableColumnCount: PropTypes.number.isRequired,
@@ -65,7 +69,7 @@ const propTypes = {
 
 const defaultProps = {};
 
-const markdownPlaceHolder = `# ✨Markdown
+const MARKDOWN_PLACE_HOLDER = `# ✨Markdown
 ## ✨Markdown
 ### ✨Markdown
 
@@ -73,6 +77,8 @@ const markdownPlaceHolder = `# ✨Markdown
 
 Click here to edit [markdown](https://bit.ly/1dQOfRK)`;
 
+const MARKDOWN_ERROR_MESSAGE = t('This markdown component has an error.');
+
 function isSafeMarkup(node) {
   if (node.type === 'html') {
     return /href="(javascript|vbscript|file):.*"/gim.test(node.value) === false;
@@ -88,6 +94,8 @@ class Markdown extends React.PureComponent {
       markdownSource: props.component.meta.code,
       editor: null,
       editorMode: 'preview',
+      undoLength: props.undoLength,
+      redoLength: props.redoLength,
     };
     this.renderStartTime = Logger.getTimestamp();
 
@@ -107,11 +115,46 @@ class Markdown extends React.PureComponent {
     });
   }
 
-  UNSAFE_componentWillReceiveProps(nextProps) {
-    const nextSource = nextProps.component.meta.code;
-    if (this.state.markdownSource !== nextSource) {
-      this.setState({ markdownSource: nextSource });
+  static getDerivedStateFromProps(nextProps, state) {
+    const {
+      hasError,
+      editorMode,
+      markdownSource,
+      undoLength,
+      redoLength,
+    } = state;
+    const {
+      component: nextComponent,
+      undoLength: nextUndoLength,
+      redoLength: nextRedoLength,
+    } = nextProps;
+    // user click undo or redo ?
+    if (nextUndoLength !== undoLength || nextRedoLength !== redoLength) {
+      return {
+        ...state,
+        undoLength: nextUndoLength,
+        redoLength: nextRedoLength,
+        markdownSource: nextComponent.meta.code,
+        hasError: false,
+      };
+    } else if (
+      !hasError &&
+      editorMode === 'preview' &&
+      nextComponent.meta.code !== markdownSource
+    ) {
+      return {
+        ...state,
+        markdownSource: nextComponent.meta.code,
+      };
     }
+
+    return state;
+  }
+
+  static getDerivedStateFromError() {
+    return {
+      hasError: true,
+    };
   }
 
   componentDidUpdate(prevProps) {
@@ -124,6 +167,16 @@ class Markdown extends React.PureComponent {
     }
   }
 
+  componentDidCatch(error, info) {
+    if (this.state.editor && this.state.editorMode === 'preview') {
+      this.props.addDangerToast(
+        t(
+          'This markdown component has an error. Please revert your recent changes.',
+        ),
+      );
+    }
+  }
+
   setEditor(editor) {
     editor.getSession().setUseWrapMode(true);
     this.setState({
@@ -139,7 +192,12 @@ class Markdown extends React.PureComponent {
   }
 
   handleChangeEditorMode(mode) {
-    if (this.state.editorMode === 'edit') {
+    const nextState = {
+      ...this.state,
+      editorMode: mode,
+    };
+
+    if (mode === 'preview') {
       const { updateComponents, component } = this.props;
       if (component.meta.code !== this.state.markdownSource) {
         updateComponents({
@@ -152,11 +210,10 @@ class Markdown extends React.PureComponent {
           },
         });
       }
+      nextState.hasError = false;
     }
 
-    this.setState(() => ({
-      editorMode: mode,
-    }));
+    this.setState(nextState);
   }
 
   handleMarkdownChange(nextValue) {
@@ -184,7 +241,7 @@ class Markdown extends React.PureComponent {
           // thisl allows "select all => delete" to give an empty editor
           typeof this.state.markdownSource === 'string'
             ? this.state.markdownSource
-            : markdownPlaceHolder
+            : MARKDOWN_PLACE_HOLDER
         }
         readOnly={false}
         onLoad={this.setEditor}
@@ -193,9 +250,14 @@ class Markdown extends React.PureComponent {
   }
 
   renderPreviewMode() {
+    const { hasError } = this.state;
     return (
       <ReactMarkdown
-        source={this.state.markdownSource || markdownPlaceHolder}
+        source={
+          hasError
+            ? MARKDOWN_ERROR_MESSAGE
+            : this.state.markdownSource || MARKDOWN_PLACE_HOLDER
+        }
         escapeHtml={false}
         allowNode={isSafeMarkup}
       />
diff --git a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
index dd2c188..e0eb38a 100644
--- a/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
+++ b/superset-frontend/src/dashboard/containers/DashboardComponent.jsx
@@ -35,6 +35,7 @@ import {
 } from '../actions/dashboardLayout';
 import { setDirectPathToChild } from '../actions/dashboardState';
 import { logEvent } from '../../logger/actions';
+import { addDangerToast } from '../../messageToasts/actions';
 
 const propTypes = {
   component: componentShape.isRequired,
@@ -66,6 +67,8 @@ function mapStateToProps(
     component,
     parentComponent: dashboardLayout[parentId],
     editMode: dashboardState.editMode,
+    undoLength: undoableLayout.past.length,
+    redoLength: undoableLayout.future.length,
     filters: getActiveFilters(),
     directPathToChild: dashboardState.directPathToChild,
     directPathLastUpdated: dashboardState.directPathLastUpdated,
@@ -97,6 +100,7 @@ function mapStateToProps(
 function mapDispatchToProps(dispatch) {
   return bindActionCreators(
     {
+      addDangerToast,
       createComponent,
       deleteComponent,
       updateComponents,