commit
9f865efcc2
|
@ -0,0 +1,82 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
import FuncArgInput, {OnChangeArg} from 'src/ifql/components/FuncArgInput'
|
||||||
|
import * as types from 'src/ifql/constants/argumentTypes'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
funcID: string
|
||||||
|
argKey: string
|
||||||
|
value: string
|
||||||
|
type: string
|
||||||
|
onChangeArg: OnChangeArg
|
||||||
|
onGenerateScript: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class FuncArg extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
argKey,
|
||||||
|
value,
|
||||||
|
type,
|
||||||
|
onChangeArg,
|
||||||
|
funcID,
|
||||||
|
onGenerateScript,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case types.STRING:
|
||||||
|
case types.DURATION:
|
||||||
|
case types.TIME:
|
||||||
|
case types.REGEXP:
|
||||||
|
case types.FLOAT:
|
||||||
|
case types.INT:
|
||||||
|
case types.UINT:
|
||||||
|
case types.ARRAY: {
|
||||||
|
return (
|
||||||
|
<FuncArgInput
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
argKey={argKey}
|
||||||
|
funcID={funcID}
|
||||||
|
onChangeArg={onChangeArg}
|
||||||
|
onGenerateScript={onGenerateScript}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.BOOL: {
|
||||||
|
// TODO: make boolean arg component
|
||||||
|
return (
|
||||||
|
<div className="func-arg">
|
||||||
|
{argKey} : {value}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case types.FUNCTION: {
|
||||||
|
// TODO: make separate function component
|
||||||
|
return (
|
||||||
|
<div className="func-arg">
|
||||||
|
{argKey} : {value}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case types.NIL: {
|
||||||
|
// TODO: handle nil type
|
||||||
|
return (
|
||||||
|
<div className="func-arg">
|
||||||
|
{argKey} : {value}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return (
|
||||||
|
<div className="func-arg">
|
||||||
|
{argKey} : {value}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FuncArg
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
|
||||||
|
|
||||||
|
export type OnChangeArg = (inputArg: InputArg) => void
|
||||||
|
|
||||||
|
export interface InputArg {
|
||||||
|
funcID: string
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
funcID: string
|
||||||
|
argKey: string
|
||||||
|
value: string
|
||||||
|
type: string
|
||||||
|
onChangeArg: OnChangeArg
|
||||||
|
onGenerateScript: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
class FuncArgInput extends PureComponent<Props> {
|
||||||
|
public render() {
|
||||||
|
const {argKey, value, type} = this.props
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label htmlFor={argKey}>{argKey}: </label>
|
||||||
|
<input
|
||||||
|
name={argKey}
|
||||||
|
value={value}
|
||||||
|
placeholder={type}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={this.handleKeyDown}
|
||||||
|
type="text"
|
||||||
|
className="form-control input-xs"
|
||||||
|
spellCheck={false}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key !== 'Enter') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.onGenerateScript()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const {funcID, argKey} = this.props
|
||||||
|
|
||||||
|
this.props.onChangeArg({funcID, key: argKey, value: e.target.value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FuncArgInput
|
|
@ -1,4 +1,6 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
import FuncArg from 'src/ifql/components/FuncArg'
|
||||||
|
import {OnChangeArg} from 'src/ifql/components/FuncArgInput'
|
||||||
|
|
||||||
interface Arg {
|
interface Arg {
|
||||||
key: string
|
key: string
|
||||||
|
@ -15,17 +17,29 @@ export interface Func {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
func: Func
|
func: Func
|
||||||
|
onChangeArg: OnChangeArg
|
||||||
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class FuncArgs extends PureComponent<Props> {
|
export default class FuncArgs extends PureComponent<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
|
const {func, onChangeArg, onGenerateScript} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="func-args">
|
<div className="func-args">
|
||||||
{this.props.func.args.map(({key, value}) => (
|
{func.args.map(({key, value, type}) => {
|
||||||
<div className="func-arg" key={key}>
|
return (
|
||||||
{key} : {value}
|
<FuncArg
|
||||||
</div>
|
funcID={func.id}
|
||||||
))}
|
key={key}
|
||||||
|
type={type}
|
||||||
|
argKey={key}
|
||||||
|
value={value}
|
||||||
|
onChangeArg={onChangeArg}
|
||||||
|
onGenerateScript={onGenerateScript}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React, {PureComponent, MouseEvent} from 'react'
|
import React, {PureComponent, MouseEvent} from 'react'
|
||||||
import FuncArgs from 'src/ifql/components/FuncArgs'
|
import FuncArgs from 'src/ifql/components/FuncArgs'
|
||||||
import {Func} from 'src/ifql/components/FuncArgs'
|
import {Func} from 'src/ifql/components/FuncArgs'
|
||||||
|
import {OnChangeArg} from 'src/ifql/components/FuncArgInput'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
func: Func
|
func: Func
|
||||||
onDelete: (id: string) => void
|
onDelete: (id: string) => void
|
||||||
|
onChangeArg: OnChangeArg
|
||||||
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -20,7 +23,7 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {func} = this.props
|
const {func, onChangeArg, onGenerateScript} = this.props
|
||||||
const {isOpen} = this.state
|
const {isOpen} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -28,7 +31,13 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
<div className="func-node--name" onClick={this.handleClick}>
|
<div className="func-node--name" onClick={this.handleClick}>
|
||||||
<div>{func.name}</div>
|
<div>{func.name}</div>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && <FuncArgs func={func} />}
|
{isOpen && (
|
||||||
|
<FuncArgs
|
||||||
|
func={func}
|
||||||
|
onChangeArg={onChangeArg}
|
||||||
|
onGenerateScript={onGenerateScript}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="btn btn-danger btn-square" onClick={this.handleDelete}>
|
<div className="btn btn-danger btn-square" onClick={this.handleDelete}>
|
||||||
<span className="icon-trash" />
|
<span className="icon-trash" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +49,7 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
this.props.onDelete(this.props.func.id)
|
this.props.onDelete(this.props.func.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClick = (e: MouseEvent<HTMLElement>) => {
|
private handleClick = (e: MouseEvent<HTMLElement>): void => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
const {isOpen} = this.state
|
const {isOpen} = this.state
|
||||||
|
|
|
@ -4,6 +4,7 @@ import FuncNode from 'src/ifql/components/FuncNode'
|
||||||
import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor'
|
import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor'
|
||||||
|
|
||||||
import {Func} from 'src/ifql/components/FuncArgs'
|
import {Func} from 'src/ifql/components/FuncArgs'
|
||||||
|
import {OnChangeArg} from 'src/ifql/components/FuncArgInput'
|
||||||
|
|
||||||
export interface Suggestion {
|
export interface Suggestion {
|
||||||
name: string
|
name: string
|
||||||
|
@ -20,6 +21,8 @@ interface Props {
|
||||||
onChangeScript: (script: string) => void
|
onChangeScript: (script: string) => void
|
||||||
onSubmitScript: (script: string) => void
|
onSubmitScript: (script: string) => void
|
||||||
onDeleteFuncNode: (id: string) => void
|
onDeleteFuncNode: (id: string) => void
|
||||||
|
onChangeArg: OnChangeArg
|
||||||
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeMachine extends PureComponent<Props> {
|
class TimeMachine extends PureComponent<Props> {
|
||||||
|
@ -28,9 +31,11 @@ class TimeMachine extends PureComponent<Props> {
|
||||||
funcs,
|
funcs,
|
||||||
script,
|
script,
|
||||||
onAddNode,
|
onAddNode,
|
||||||
|
onChangeArg,
|
||||||
onChangeScript,
|
onChangeScript,
|
||||||
onSubmitScript,
|
onSubmitScript,
|
||||||
onDeleteFuncNode,
|
onDeleteFuncNode,
|
||||||
|
onGenerateScript,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -42,7 +47,13 @@ class TimeMachine extends PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
<div className="func-nodes-container">
|
<div className="func-nodes-container">
|
||||||
{funcs.map(f => (
|
{funcs.map(f => (
|
||||||
<FuncNode key={f.id} func={f} onDelete={onDeleteFuncNode} />
|
<FuncNode
|
||||||
|
key={f.id}
|
||||||
|
func={f}
|
||||||
|
onChangeArg={onChangeArg}
|
||||||
|
onDelete={onDeleteFuncNode}
|
||||||
|
onGenerateScript={onGenerateScript}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<FuncSelector funcs={this.funcNames} onAddNode={onAddNode} />
|
<FuncSelector funcs={this.funcNames} onAddNode={onAddNode} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export const INVALID = 'invalid'
|
||||||
|
export const NIL = 'nil'
|
||||||
|
export const STRING = 'string'
|
||||||
|
export const INT = 'int'
|
||||||
|
export const UINT = 'uint'
|
||||||
|
export const FLOAT = 'float'
|
||||||
|
export const BOOL = 'bool'
|
||||||
|
export const TIME = 'time'
|
||||||
|
export const DURATION = 'duration'
|
||||||
|
export const REGEXP = 'regexp'
|
||||||
|
export const ARRAY = 'array'
|
||||||
|
export const OBJECT = 'object'
|
||||||
|
export const FUNCTION = 'function'
|
|
@ -7,8 +7,10 @@ import _ from 'lodash'
|
||||||
import TimeMachine, {Suggestion} from 'src/ifql/components/TimeMachine'
|
import TimeMachine, {Suggestion} from 'src/ifql/components/TimeMachine'
|
||||||
import Walker from 'src/ifql/ast/walker'
|
import Walker from 'src/ifql/ast/walker'
|
||||||
import {Func} from 'src/ifql/components/FuncArgs'
|
import {Func} from 'src/ifql/components/FuncArgs'
|
||||||
|
import {InputArg} from 'src/ifql/components/FuncArgInput'
|
||||||
|
|
||||||
import {getSuggestions, getAST} from 'src/ifql/apis'
|
import {getSuggestions, getAST} from 'src/ifql/apis'
|
||||||
|
import * as argTypes from 'src/ifql/constants/argumentTypes'
|
||||||
|
|
||||||
interface Links {
|
interface Links {
|
||||||
self: string
|
self: string
|
||||||
|
@ -70,9 +72,11 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
funcs={this.state.funcs}
|
funcs={this.state.funcs}
|
||||||
suggestions={suggestions}
|
suggestions={suggestions}
|
||||||
onAddNode={this.handleAddNode}
|
onAddNode={this.handleAddNode}
|
||||||
|
onChangeArg={this.handleChangeArg}
|
||||||
onSubmitScript={this.getASTResponse}
|
onSubmitScript={this.getASTResponse}
|
||||||
onChangeScript={this.handleChangeScript}
|
onChangeScript={this.handleChangeScript}
|
||||||
onDeleteFuncNode={this.handleDeleteFuncNode}
|
onDeleteFuncNode={this.handleDeleteFuncNode}
|
||||||
|
onGenerateScript={this.handleGenerateScript}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,6 +84,50 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleGenerateScript = (): void => {
|
||||||
|
this.getASTResponse(this.funcsToScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleChangeArg = ({funcID, key, value}: InputArg): void => {
|
||||||
|
const funcs = this.state.funcs.map(f => {
|
||||||
|
if (f.id !== funcID) {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = f.args.map(a => {
|
||||||
|
if (a.key === key) {
|
||||||
|
return {...a, value}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
|
||||||
|
return {...f, args}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({funcs})
|
||||||
|
}
|
||||||
|
|
||||||
|
private get funcsToScript(): string {
|
||||||
|
return this.state.funcs
|
||||||
|
.map(func => `${func.name}(${this.argsToScript(func.args)})`)
|
||||||
|
.join('\n\t|> ')
|
||||||
|
}
|
||||||
|
|
||||||
|
private argsToScript(args): string {
|
||||||
|
const withValues = args.filter(arg => arg.value)
|
||||||
|
|
||||||
|
return withValues
|
||||||
|
.map(({key, value, type}) => {
|
||||||
|
if (type === argTypes.STRING) {
|
||||||
|
return `${key}: "${value}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${key}: ${value}`
|
||||||
|
})
|
||||||
|
.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
private handleChangeScript = (script: string): void => {
|
private handleChangeScript = (script: string): void => {
|
||||||
this.setState({script})
|
this.setState({script})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
import FuncArg from 'src/ifql/components/FuncArg'
|
||||||
|
|
||||||
|
const setup = () => {
|
||||||
|
const props = {
|
||||||
|
funcID: '',
|
||||||
|
argKey: '',
|
||||||
|
value: '',
|
||||||
|
type: '',
|
||||||
|
onChangeArg: () => {},
|
||||||
|
onGenerateScript: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = shallow(<FuncArg {...props} />)
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('IFQL.Components.FuncArg', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders without errors', () => {
|
||||||
|
const {wrapper} = setup()
|
||||||
|
|
||||||
|
expect(wrapper.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,78 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
import FuncArgInput from 'src/ifql/components/FuncArgInput'
|
||||||
|
|
||||||
|
const setup = (override?) => {
|
||||||
|
const props = {
|
||||||
|
funcID: '1',
|
||||||
|
argKey: 'db',
|
||||||
|
value: 'db1',
|
||||||
|
type: 'string',
|
||||||
|
onChangeArg: () => {},
|
||||||
|
onGenerateScript: () => {},
|
||||||
|
...override,
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = shallow(<FuncArgInput {...props} />)
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper,
|
||||||
|
props,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('IFQL.Components.FuncArgInput', () => {
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders without errors', () => {
|
||||||
|
const {wrapper} = setup()
|
||||||
|
|
||||||
|
expect(wrapper.exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('user interraction', () => {
|
||||||
|
describe('typing', () => {
|
||||||
|
describe('hitting enter', () => {
|
||||||
|
it('generates a new script when Enter is pressed', () => {
|
||||||
|
const onGenerateScript = jest.fn()
|
||||||
|
const preventDefault = jest.fn()
|
||||||
|
|
||||||
|
const {wrapper} = setup({onGenerateScript})
|
||||||
|
|
||||||
|
const input = wrapper.find('input')
|
||||||
|
input.simulate('keydown', {key: 'Enter', preventDefault})
|
||||||
|
|
||||||
|
expect(onGenerateScript).toHaveBeenCalledTimes(1)
|
||||||
|
expect(preventDefault).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('it does not generate a new script when typing', () => {
|
||||||
|
const onGenerateScript = jest.fn()
|
||||||
|
const preventDefault = jest.fn()
|
||||||
|
|
||||||
|
const {wrapper} = setup({onGenerateScript})
|
||||||
|
|
||||||
|
const input = wrapper.find('input')
|
||||||
|
input.simulate('keydown', {key: 'a', preventDefault})
|
||||||
|
|
||||||
|
expect(onGenerateScript).not.toHaveBeenCalled()
|
||||||
|
expect(preventDefault).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('changing the input value', () => {
|
||||||
|
it('calls onChangeArg', () => {
|
||||||
|
const onChangeArg = jest.fn()
|
||||||
|
const {wrapper, props} = setup({onChangeArg})
|
||||||
|
|
||||||
|
const input = wrapper.find('input')
|
||||||
|
const value = 'db2'
|
||||||
|
input.simulate('change', {target: {value}})
|
||||||
|
const {funcID, argKey} = props
|
||||||
|
|
||||||
|
expect(onChangeArg).toHaveBeenCalledWith({funcID, key: argKey, value})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -11,6 +11,8 @@ const setup = () => {
|
||||||
onChangeScript: () => {},
|
onChangeScript: () => {},
|
||||||
onSubmitScript: () => {},
|
onSubmitScript: () => {},
|
||||||
onDeleteFuncNode: () => {},
|
onDeleteFuncNode: () => {},
|
||||||
|
onChangeArg: () => {},
|
||||||
|
onGenerateScript: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapper = shallow(<TimeMachine {...props} />)
|
const wrapper = shallow(<TimeMachine {...props} />)
|
||||||
|
|
Loading…
Reference in New Issue