Insert flux function at or below cursor line
parent
a7843d0451
commit
9d7b70a860
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue