Insert flux function at or below cursor line

pull/5265/head
Iris Scholten 2019-03-14 17:40:39 -07:00
parent a7843d0451
commit 9d7b70a860
6 changed files with 152 additions and 24 deletions

View File

@ -2,6 +2,7 @@
### Bug Fixes ### Bug Fixes
1. [#5110](https://github.com/influxdata/chronograf/pull/5110): Fix the input for line controls in visualization options. 1. [#5110](https://github.com/influxdata/chronograf/pull/5110): Fix the input for line controls in visualization options.
1. [#5111](https://github.com/influxdata/chronograf/pull/5111): Stop scrollbars from covering text in flux editor 1. [#5111](https://github.com/influxdata/chronograf/pull/5111): Stop scrollbars from covering text in flux editor
1. [#5114](https://github.com/influxdata/chronograf/pull/5114): Insert flux function near cursor in flux editor
## v1.7.8 [2019-02-08] ## v1.7.8 [2019-02-08]
### Bug Fixes ### Bug Fixes

View File

@ -1,5 +1,6 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import {Position} from 'codemirror'
// Components // Components
import FluxScriptEditor from 'src/flux/components/FluxScriptEditor' import FluxScriptEditor from 'src/flux/components/FluxScriptEditor'
@ -16,6 +17,7 @@ interface Props {
onChangeScript: (draftScript: string) => void onChangeScript: (draftScript: string) => void
onSubmitScript: () => void onSubmitScript: () => void
onShowWizard: () => void onShowWizard: () => void
onCursorChange?: (position: Position) => void
} }
class FluxEditor extends PureComponent<Props> { class FluxEditor extends PureComponent<Props> {
@ -28,6 +30,7 @@ class FluxEditor extends PureComponent<Props> {
onChangeScript, onChangeScript,
onSubmitScript, onSubmitScript,
onShowWizard, onShowWizard,
onCursorChange,
} = this.props } = this.props
return ( return (
@ -39,6 +42,7 @@ class FluxEditor extends PureComponent<Props> {
suggestions={suggestions} suggestions={suggestions}
onChangeScript={onChangeScript} onChangeScript={onChangeScript}
onSubmitScript={onSubmitScript} onSubmitScript={onSubmitScript}
onCursorChange={onCursorChange}
> >
{script.trim() === '' && ( {script.trim() === '' && (
<div className="flux-script-wizard--bg-hint"> <div className="flux-script-wizard--bg-hint">

View File

@ -1,6 +1,6 @@
import React, {PureComponent, MouseEvent} from 'react' import React, {PureComponent, MouseEvent} from 'react'
import {Controlled as ReactCodeMirror, IInstance} from 'react-codemirror2' import {Controlled as ReactCodeMirror, IInstance} from 'react-codemirror2'
import {EditorChange, LineWidget} from 'codemirror' import {EditorChange, LineWidget, Position} from 'codemirror'
import {ShowHintOptions} from 'src/types/codemirror' import {ShowHintOptions} from 'src/types/codemirror'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeScript, Suggestion} from 'src/types/flux' import {OnChangeScript, Suggestion} from 'src/types/flux'
@ -24,6 +24,7 @@ interface Props {
onChangeScript: OnChangeScript onChangeScript: OnChangeScript
onSubmitScript: () => void onSubmitScript: () => void
suggestions: Suggestion[] suggestions: Suggestion[]
onCursorChange?: (position: Position) => void
} }
interface Widget extends LineWidget { interface Widget extends LineWidget {
@ -32,6 +33,7 @@ interface Widget extends LineWidget {
interface State { interface State {
script: string script: string
cursorPosition: Position
} }
interface EditorInstance extends IInstance { interface EditorInstance extends IInstance {
@ -51,6 +53,7 @@ class FluxScriptEditor extends PureComponent<Props, State> {
super(props) super(props)
this.state = { this.state = {
script: props.script, script: props.script,
cursorPosition: null,
} }
} }
@ -108,12 +111,21 @@ class FluxScriptEditor extends PureComponent<Props, State> {
onTouchStart={this.onTouchStart} onTouchStart={this.onTouchStart}
editorDidMount={this.handleMount} editorDidMount={this.handleMount}
onKeyUp={this.handleKeyUp} onKeyUp={this.handleKeyUp}
onCursor={this.handleCursorChange}
/> />
{children} {children}
</div> </div>
) )
} }
private handleCursorChange = (__: IInstance, position: Position) => {
const {onCursorChange} = this.props
if (onCursorChange) {
onCursorChange(position)
}
}
private handleMouseEnter = (e: MouseEvent<HTMLDivElement>) => { private handleMouseEnter = (e: MouseEvent<HTMLDivElement>) => {
const {width, height} = e.currentTarget.getBoundingClientRect() const {width, height} = e.currentTarget.getBoundingClientRect()
const {width: prevWidth, height: prevHeight} = this.containerDimensions const {width: prevWidth, height: prevHeight} = this.containerDimensions

View File

@ -9,7 +9,7 @@ import SearchBar from 'src/flux/components/flux_functions_toolbar/SearchBar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
// Constants // Constants
import {FUNCTIONS, FROM, UNION} from 'src/flux/constants/functions' import {FUNCTIONS} from 'src/flux/constants/functions'
// Utils // Utils
import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer'
@ -17,20 +17,28 @@ import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer'
// Decorators // Decorators
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface PassedProps {
onInsertFluxFunction: (functionName: string, text: string) => void
}
interface ConnectedProps { interface ConnectedProps {
script: string script: string
onUpdateScript: (script: string) => void onUpdateScript: (script: string) => void
} }
type Props = PassedProps & ConnectedProps
interface State { interface State {
searchTerm: string searchTerm: string
} }
@ErrorHandling @ErrorHandling
class FluxFunctionsToolbar extends PureComponent<ConnectedProps, State> { class FluxFunctionsToolbar extends PureComponent<Props, State> {
public constructor(props) { public constructor(props: Props) {
super(props) super(props)
this.state = {searchTerm: ''} this.state = {searchTerm: ''}
} }
public render() { public render() {
const {searchTerm} = this.state const {searchTerm} = this.state
return ( return (
@ -50,7 +58,7 @@ class FluxFunctionsToolbar extends PureComponent<ConnectedProps, State> {
key={category} key={category}
category={category} category={category}
funcs={funcs} funcs={funcs}
onClickFunction={this.handleUpdateScript} onClickFunction={this.handleClickFunction}
/> />
) )
} }
@ -63,32 +71,23 @@ class FluxFunctionsToolbar extends PureComponent<ConnectedProps, State> {
) )
} }
private handleClickFunction = (
fluxFunction: string,
funcExample: string
): void => {
this.props.onInsertFluxFunction(fluxFunction, funcExample)
}
private handleSearch = (searchTerm: string): void => { private handleSearch = (searchTerm: string): void => {
this.setState({searchTerm}) this.setState({searchTerm})
} }
private handleUpdateScript = (funcName: string, funcExample: string) => {
const {script, onUpdateScript} = this.props
switch (funcName) {
case FROM.name: {
onUpdateScript(`${script}\n${funcExample}`)
return
}
case UNION.name: {
onUpdateScript(`${script.trimRight()}\n\n${funcExample}`)
return
}
default:
onUpdateScript(`${script}\n |> ${funcExample}`)
}
}
} }
const ConnectedFluxFunctionsToolbar = () => ( const ConnectedFluxFunctionsToolbar = (props: PassedProps) => (
<Subscribe to={[TimeMachineContainer]}> <Subscribe to={[TimeMachineContainer]}>
{(container: TimeMachineContainer) => ( {(container: TimeMachineContainer) => (
<FluxFunctionsToolbar <FluxFunctionsToolbar
{...props}
script={container.state.draftScript} script={container.state.draftScript}
onUpdateScript={container.handleUpdateDraftScript} onUpdateScript={container.handleUpdateDraftScript}
/> />

View File

@ -0,0 +1,82 @@
import {Position} from 'codemirror'
// Constants
import {FROM, UNION} from 'src/flux/constants/functions'
const rejoinScript = (scriptLines: string[]): string => {
return scriptLines.join('\n')
}
const insertAtLine = (
lineNumber: number,
scriptLines: string[],
textToInsert: string,
insertOnSameLine?: boolean
): string => {
const front = scriptLines.slice(0, lineNumber)
const backStartIndex = insertOnSameLine ? lineNumber + 1 : lineNumber
const back = scriptLines.slice(backStartIndex)
const updated = [...front, textToInsert, ...back]
return rejoinScript(updated)
}
const getInsertLineNumber = (
currentLineNumber: number,
scriptLines: string[]
): number => {
const currentLine = scriptLines[currentLineNumber]
// Insert on the current line if its an empty line
if (!currentLine.trim()) {
return currentLineNumber
}
return currentLineNumber + 1
}
const formatFunctionForInsert = (funcName: string, fluxFunction: string) => {
switch (funcName) {
case FROM.name:
case UNION.name: {
return `\n${fluxFunction}`
}
default:
return ` |> ${fluxFunction}`
}
}
const getCursorPosition = (insertLineNumber, formattedFunction): Position => {
const endOfLine = formattedFunction.length - 1
return {line: insertLineNumber, ch: endOfLine}
}
export const insertFluxFunction = (
currentLineNumber: number,
currentScript: string,
functionName: string,
fluxFunction: string
): {updatedScript: string; cursorPosition: Position} => {
const scriptLines = currentScript.split('\n')
const insertLineNumber = getInsertLineNumber(currentLineNumber, scriptLines)
const insertOnSameLine = currentLineNumber === insertLineNumber
const formattedFunction = formatFunctionForInsert(functionName, fluxFunction)
const updatedScript = insertAtLine(
insertLineNumber,
scriptLines,
formattedFunction,
insertOnSameLine
)
const updatedCursorPosition = getCursorPosition(
insertLineNumber,
formattedFunction
)
return {updatedScript, cursorPosition: updatedCursorPosition}
}

View File

@ -1,6 +1,7 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import {Subscribe} from 'unstated' import {Subscribe} from 'unstated'
import {Position} from 'codemirror'
// Components // Components
import SchemaExplorer from 'src/flux/components/SchemaExplorer' import SchemaExplorer from 'src/flux/components/SchemaExplorer'
@ -20,6 +21,7 @@ import DefaultDebouncer, {Debouncer} from 'src/shared/utils/debouncer'
import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer'
import {parseError} from 'src/flux/helpers/scriptBuilder' import {parseError} from 'src/flux/helpers/scriptBuilder'
import {getSuggestions} from 'src/flux/helpers/suggestions' import {getSuggestions} from 'src/flux/helpers/suggestions'
import {insertFluxFunction} from 'src/flux/helpers/scriptInsertion'
// Types // Types
import {NotificationAction, Source} from 'src/types' import {NotificationAction, Source} from 'src/types'
@ -56,6 +58,7 @@ interface State {
class FluxQueryMaker extends PureComponent<Props, State> { class FluxQueryMaker extends PureComponent<Props, State> {
private debouncer: Debouncer = new DefaultDebouncer() private debouncer: Debouncer = new DefaultDebouncer()
private getAST = restartable(getAST) private getAST = restartable(getAST)
private cursorPosition: Position
public constructor(props: Props) { public constructor(props: Props) {
super(props) super(props)
@ -122,6 +125,7 @@ class FluxQueryMaker extends PureComponent<Props, State> {
onChangeScript={this.handleChangeDraftScript} onChangeScript={this.handleChangeDraftScript}
onSubmitScript={this.handleSubmitScript} onSubmitScript={this.handleSubmitScript}
onShowWizard={this.handleShowWizard} onShowWizard={this.handleShowWizard}
onCursorChange={this.handleCursorPosition}
/> />
), ),
}, },
@ -130,7 +134,11 @@ class FluxQueryMaker extends PureComponent<Props, State> {
size: rightSize, size: rightSize,
headerButtons: [], headerButtons: [],
menuOptions: [], menuOptions: [],
render: () => <FluxFunctionsToolbar />, render: () => (
<FluxFunctionsToolbar
onInsertFluxFunction={this.handleInsertFluxFunction}
/>
),
headerOrientation: HANDLE_VERTICAL, headerOrientation: HANDLE_VERTICAL,
}, },
] ]
@ -152,6 +160,28 @@ class FluxQueryMaker extends PureComponent<Props, State> {
) )
} }
private handleCursorPosition = (position: Position): void => {
this.cursorPosition = position
}
private handleInsertFluxFunction = async (
functionName: string,
fluxFunction: string
): Promise<void> => {
const {draftScript} = this.props
const {line} = this.cursorPosition
const {updatedScript, cursorPosition} = insertFluxFunction(
line,
draftScript,
functionName,
fluxFunction
)
await this.handleChangeDraftScript(updatedScript)
this.handleCursorPosition(cursorPosition)
}
private handleSubmitScript = () => { private handleSubmitScript = () => {
const { const {
onChangeScript, onChangeScript,