[dashboard] handle markdown error (#9350)

* [dashboard] handle markdown error

* localize error message, fix review comments.
This commit is contained in:
Grace Guo 2020-03-26 09:06:23 -07:00 committed by GitHub
parent d5e2f7e6b3
commit 5b07c8d229
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 11 deletions

View File

@ -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}
/>

View File

@ -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,