Merge pull request #3595 from influxdata/flux/char-error

Flux Page Error Robustness
pull/3599/head
Andrew Watkins 2018-06-07 14:39:16 -07:00 committed by GitHub
commit 34916b7f99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 14 deletions

View File

@ -42,6 +42,8 @@ interface State {
}
export default class TagListItem extends PureComponent<Props, State> {
private debouncedOnSearch: () => void
constructor(props) {
super(props)
@ -167,8 +169,6 @@ export default class TagListItem extends PureComponent<Props, State> {
)
}
private debouncedOnSearch() {} // See constructor
private handleInputClick = (e: MouseEvent<HTMLDivElement>): void => {
e.stopPropagation()
}

View File

@ -1,6 +1,6 @@
import React, {PureComponent} from 'react'
import {Controlled as ReactCodeMirror, IInstance} from 'react-codemirror2'
import {EditorChange} from 'codemirror'
import {EditorChange, LineWidget} from 'codemirror'
import {ShowHintOptions} from 'src/types/codemirror'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeScript, OnSubmitScript, Suggestion} from 'src/types/flux'
@ -27,13 +27,22 @@ interface Props {
suggestions: Suggestion[]
}
interface Widget extends LineWidget {
node: HTMLElement
}
interface State {
lineWidgets: Widget[]
}
interface EditorInstance extends IInstance {
showHint: (options?: ShowHintOptions) => void
}
@ErrorHandling
class TimeMachineEditor extends PureComponent<Props> {
class TimeMachineEditor extends PureComponent<Props, State> {
private editor: EditorInstance
private lineWidgets: Widget[] = []
constructor(props) {
super(props)
@ -48,6 +57,7 @@ class TimeMachineEditor extends PureComponent<Props> {
if (status.type !== 'error') {
this.editor.clearGutter('error-gutter')
this.clearWidgets()
}
if (prevProps.visibility === visibility) {
@ -63,13 +73,13 @@ class TimeMachineEditor extends PureComponent<Props> {
const {script} = this.props
const options = {
lineNumbers: true,
theme: 'time-machine',
tabIndex: 1,
readonly: false,
completeSingle: false,
autoRefresh: true,
mode: 'flux',
readonly: false,
lineNumbers: true,
autoRefresh: true,
theme: 'time-machine',
completeSingle: false,
gutters: ['error-gutter'],
}
@ -98,23 +108,55 @@ class TimeMachineEditor extends PureComponent<Props> {
this.editor.clearGutter('error-gutter')
const lineNumbers = this.statusLine
lineNumbers.forEach(({line, text}) => {
const lineNumber = line - 1
this.editor.setGutterMarker(
line - 1,
lineNumber,
'error-gutter',
this.errorMarker(text)
this.errorMarker(text, lineNumber)
)
})
this.editor.refresh()
}
private errorMarker(message: string): HTMLElement {
private errorMarker(message: string, line: number): HTMLElement {
const span = document.createElement('span')
span.className = 'icon stop error-warning'
span.title = message
span.addEventListener('click', this.handleClickError(message, line))
return span
}
private handleClickError = (text: string, line: number) => () => {
let widget = this.lineWidgets.find(w => w.node.textContent === text)
if (widget) {
return this.clearWidget(widget)
}
const errorDiv = document.createElement('div')
errorDiv.className = 'inline-error-message'
errorDiv.innerText = text
widget = this.editor.addLineWidget(line, errorDiv) as Widget
this.lineWidgets = [...this.lineWidgets, widget]
}
private clearWidget = (widget: Widget): void => {
widget.clear()
this.lineWidgets = this.lineWidgets.filter(
w => w.node.textContent !== widget.node.textContent
)
}
private clearWidgets = () => {
this.lineWidgets.forEach(w => {
w.clear()
})
this.lineWidgets = []
}
private get statusLine(): Gutter[] {
const {status} = this.props
const messages = status.text.split('\n')

View File

@ -54,10 +54,14 @@ interface State {
suggestions: Suggestion[]
}
type ScriptFunc = (script: string) => void
export const FluxContext = React.createContext()
@ErrorHandling
export class FluxPage extends PureComponent<Props, State> {
private debouncedASTResponse: ScriptFunc
constructor(props) {
super(props)
this.state = {
@ -70,6 +74,10 @@ export class FluxPage extends PureComponent<Props, State> {
text: '',
},
}
this.debouncedASTResponse = _.debounce(script => {
this.getASTResponse(script, false)
}, 250)
}
public async componentDidMount() {
@ -288,6 +296,7 @@ export class FluxPage extends PureComponent<Props, State> {
}
private handleChangeScript = (script: string): void => {
this.debouncedASTResponse(script)
this.props.updateScript(script)
}
@ -405,7 +414,7 @@ export class FluxPage extends PureComponent<Props, State> {
}
}
private getASTResponse = async (script: string) => {
private getASTResponse = async (script: string, update: boolean = true) => {
const {links} = this.props
if (!script) {
@ -414,10 +423,14 @@ export class FluxPage extends PureComponent<Props, State> {
try {
const ast = await getAST({url: links.ast, body: script})
if (update) {
this.props.updateScript(script)
}
const body = bodyNodes(ast, this.state.suggestions)
const status = {type: 'success', text: ''}
this.setState({ast, body, status})
this.props.updateScript(script)
} catch (error) {
this.setState({status: this.parseError(error)})
return console.error('Could not parse AST', error)

View File

@ -23,4 +23,9 @@
.error-warning {
color: $c-dreamsicle;
cursor: pointer;
}
.inline-error-message {
color: white;
background-color: red;
}