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
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. [#5114](https://github.com/influxdata/chronograf/pull/5114): Insert flux function near cursor in flux editor
## v1.7.8 [2019-02-08]
### Bug Fixes

View File

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

View File

@ -1,6 +1,6 @@
import React, {PureComponent, MouseEvent} from 'react'
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 {ErrorHandling} from 'src/shared/decorators/errors'
import {OnChangeScript, Suggestion} from 'src/types/flux'
@ -24,6 +24,7 @@ interface Props {
onChangeScript: OnChangeScript
onSubmitScript: () => void
suggestions: Suggestion[]
onCursorChange?: (position: Position) => void
}
interface Widget extends LineWidget {
@ -32,6 +33,7 @@ interface Widget extends LineWidget {
interface State {
script: string
cursorPosition: Position
}
interface EditorInstance extends IInstance {
@ -51,6 +53,7 @@ class FluxScriptEditor extends PureComponent<Props, State> {
super(props)
this.state = {
script: props.script,
cursorPosition: null,
}
}
@ -108,12 +111,21 @@ class FluxScriptEditor extends PureComponent<Props, State> {
onTouchStart={this.onTouchStart}
editorDidMount={this.handleMount}
onKeyUp={this.handleKeyUp}
onCursor={this.handleCursorChange}
/>
{children}
</div>
)
}
private handleCursorChange = (__: IInstance, position: Position) => {
const {onCursorChange} = this.props
if (onCursorChange) {
onCursorChange(position)
}
}
private handleMouseEnter = (e: MouseEvent<HTMLDivElement>) => {
const {width, height} = e.currentTarget.getBoundingClientRect()
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'
// Constants
import {FUNCTIONS, FROM, UNION} from 'src/flux/constants/functions'
import {FUNCTIONS} from 'src/flux/constants/functions'
// Utils
import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer'
@ -17,20 +17,28 @@ import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface PassedProps {
onInsertFluxFunction: (functionName: string, text: string) => void
}
interface ConnectedProps {
script: string
onUpdateScript: (script: string) => void
}
type Props = PassedProps & ConnectedProps
interface State {
searchTerm: string
}
@ErrorHandling
class FluxFunctionsToolbar extends PureComponent<ConnectedProps, State> {
public constructor(props) {
class FluxFunctionsToolbar extends PureComponent<Props, State> {
public constructor(props: Props) {
super(props)
this.state = {searchTerm: ''}
}
public render() {
const {searchTerm} = this.state
return (
@ -50,7 +58,7 @@ class FluxFunctionsToolbar extends PureComponent<ConnectedProps, State> {
key={category}
category={category}
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 => {
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]}>
{(container: TimeMachineContainer) => (
<FluxFunctionsToolbar
{...props}
script={container.state.draftScript}
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
import React, {PureComponent} from 'react'
import {Subscribe} from 'unstated'
import {Position} from 'codemirror'
// Components
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 {parseError} from 'src/flux/helpers/scriptBuilder'
import {getSuggestions} from 'src/flux/helpers/suggestions'
import {insertFluxFunction} from 'src/flux/helpers/scriptInsertion'
// Types
import {NotificationAction, Source} from 'src/types'
@ -56,6 +58,7 @@ interface State {
class FluxQueryMaker extends PureComponent<Props, State> {
private debouncer: Debouncer = new DefaultDebouncer()
private getAST = restartable(getAST)
private cursorPosition: Position
public constructor(props: Props) {
super(props)
@ -122,6 +125,7 @@ class FluxQueryMaker extends PureComponent<Props, State> {
onChangeScript={this.handleChangeDraftScript}
onSubmitScript={this.handleSubmitScript}
onShowWizard={this.handleShowWizard}
onCursorChange={this.handleCursorPosition}
/>
),
},
@ -130,7 +134,11 @@ class FluxQueryMaker extends PureComponent<Props, State> {
size: rightSize,
headerButtons: [],
menuOptions: [],
render: () => <FluxFunctionsToolbar />,
render: () => (
<FluxFunctionsToolbar
onInsertFluxFunction={this.handleInsertFluxFunction}
/>
),
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 = () => {
const {
onChangeScript,