Func item triggers a handler when it is clicked

Co-authored-by: Brandon Farmer <bthesorceror@gmail.com>
Co-authored-by: Andrew Watkins<andrew.watkinz@gmail.com>
pull/10616/head
Brandon Farmer 2018-03-28 14:57:55 -07:00
parent 22fc9fb263
commit 5353c0bb57
12 changed files with 215 additions and 34 deletions

View File

@ -6,6 +6,7 @@ import (
)
type getIFQLLinksResponse struct {
AST string `json:"ast"`
Self string `json:"self"`
Suggestions string `json:"suggestions"`
}

View File

@ -97,6 +97,7 @@ func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
},
IFQL: getIFQLLinksResponse{
Self: "/chronograf/v1/ifql",
AST: "/chronograf/v1/ifql/ast",
Suggestions: "/chronograf/v1/ifql/suggestions",
},
}

View File

@ -12,3 +12,25 @@ export const getSuggestions = async (url: string) => {
throw error
}
}
interface ASTRequest {
url: string
body: string
}
export const getAST = async (request: ASTRequest) => {
const {url, body} = request
try {
const {data} = await AJAX({
method: 'POST',
url,
data: {body},
})
return data
} catch (error) {
console.error('Could not parse query', error)
throw error
}
}

View File

@ -21,6 +21,10 @@ export default class Walker {
}
private reduceArgs = args => {
if (!args) {
return []
}
return args.reduce(
(acc, arg) => [...acc, ...this.getProperties(arg.properties)],
[]
@ -52,7 +56,7 @@ export default class Walker {
private getProperties = props => {
return props.map(prop => ({
name: prop.key.name,
key: prop.key.name,
value: get(prop, 'value.value', get(prop, 'value.location.source', '')),
}))
}

View File

@ -0,0 +1,43 @@
import React, {SFC, ChangeEvent, KeyboardEvent} from 'react'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import DropdownInput from 'src/shared/components/DropdownInput'
import FuncListItem from 'src/ifql/components/FuncListItem'
interface Props {
inputText: string
isOpen: boolean
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void
onAddNode: (name: string) => void
funcs: string[]
}
const FuncList: SFC<Props> = ({
inputText,
isOpen,
onAddNode,
onKeyDown,
onInputChange,
funcs,
}) => {
return (
<ul className="dropdown-menu funcs">
<DropdownInput
buttonSize="btn-xs"
buttonColor="btn-default"
onFilterChange={onInputChange}
onFilterKeyPress={onKeyDown}
searchTerm={inputText}
/>
<FancyScrollbar autoHide={false} autoHeight={true} maxHeight={240}>
{isOpen &&
funcs.map((func, i) => (
<FuncListItem key={i} name={func} onAddNode={onAddNode} />
))}
</FancyScrollbar>
</ul>
)
}
export default FuncList

View File

@ -0,0 +1,22 @@
import React, {PureComponent} from 'react'
interface Props {
name: string
onAddNode: (name: string) => void
}
export default class FuncListItem extends PureComponent<Props> {
public render() {
const {name} = this.props
return (
<li onClick={this.handleClick} className="dropdown-item func">
<a>{name}</a>
</li>
)
}
private handleClick = () => {
this.props.onAddNode(this.props.name)
}
}

View File

@ -1,9 +1,7 @@
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import DropdownInput from 'src/shared/components/DropdownInput'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import FuncList from 'src/ifql/components/FuncList'
interface State {
isOpen: boolean
@ -12,9 +10,10 @@ interface State {
interface Props {
funcs: string[]
onAddNode: (name: string) => void
}
export class FuncsButton extends PureComponent<Props, State> {
export class FuncSelector extends PureComponent<Props, State> {
constructor(props) {
super(props)
@ -25,39 +24,39 @@ export class FuncsButton extends PureComponent<Props, State> {
}
public render() {
const {onAddNode} = this.props
const {isOpen, inputText} = this.state
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div className={`dropdown dashboard-switcher ${isOpen ? 'open' : ''}`}>
<div className={`dropdown dashboard-switcher ${this.openClass}`}>
<button
className="btn btn-square btn-default btn-sm dropdown-toggle"
onClick={this.handleClick}
>
<span className="icon plus" />
</button>
<ul className="dropdown-menu funcs">
<DropdownInput
buttonSize="btn-xs"
buttonColor="btn-default"
onFilterChange={this.handleInputChange}
onFilterKeyPress={this.handleKeyDown}
searchTerm={inputText}
/>
<FancyScrollbar autoHide={false} autoHeight={true} maxHeight={240}>
{isOpen &&
this.availableFuncs.map((func, i) => (
<li className="dropdown-item func" key={i}>
<a>{func}</a>
</li>
))}
</FancyScrollbar>
</ul>
<FuncList
inputText={inputText}
onAddNode={onAddNode}
isOpen={isOpen}
funcs={this.availableFuncs}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
/>
</div>
</ClickOutside>
)
}
private get openClass(): string {
if (this.state.isOpen) {
return 'open'
}
return ''
}
private get availableFuncs(): string[] {
return this.props.funcs.filter(f =>
f.toLowerCase().includes(this.state.inputText)
@ -85,4 +84,4 @@ export class FuncsButton extends PureComponent<Props, State> {
}
}
export default FuncsButton
export default FuncSelector

View File

@ -0,0 +1,25 @@
import React, {SFC} from 'react'
interface Arg {
key: string
value: string
}
interface Node {
name: string
arguments: Arg[]
}
interface Props {
node: Node
}
const Node: SFC<Props> = ({node}) => {
return (
<div>
<div>{node.name}</div>
</div>
)
}
export default Node

View File

@ -1,13 +1,30 @@
import React, {SFC} from 'react'
import FuncsButton from 'src/ifql/components/FuncsButton'
import FuncSelector from 'src/ifql/components/FuncSelector'
import Node from 'src/ifql/components/Node'
interface Arg {
key: string
value: string
}
interface NodeProp {
name: string
arguments: Arg[]
}
interface Props {
funcs: string[]
ast: object
nodes: NodeProp[]
onAddNode: (name: string) => void
}
const TimeMachine: SFC<Props> = ({funcs}) => {
return <FuncsButton funcs={funcs} />
const TimeMachine: SFC<Props> = ({funcs, nodes, onAddNode}) => {
return (
<div>
{nodes.map((n, i) => <Node key={i} node={n} />)}
<FuncSelector funcs={funcs} onAddNode={onAddNode} />
</div>
)
}
export default TimeMachine

View File

@ -3,13 +3,14 @@ import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import TimeMachine from 'src/ifql/components/TimeMachine'
import Walker from 'src/ifql/ast/walker'
import {getSuggestions} from 'src/ifql/apis'
import {ast} from 'src/ifql/constants'
import {getSuggestions, getAST} from 'src/ifql/apis'
interface Links {
self: string
suggestions: string
ast: string
}
interface Props {
@ -18,6 +19,7 @@ interface Props {
interface State {
funcs: string[]
ast: object
}
export class IFQLPage extends PureComponent<Props, State> {
@ -25,11 +27,14 @@ export class IFQLPage extends PureComponent<Props, State> {
super(props)
this.state = {
funcs: [],
ast: null,
}
}
public async componentDidMount() {
const {suggestions} = this.props.links
const {links} = this.props
const {suggestions} = links
const baseQuery = 'from()'
try {
const results = await getSuggestions(suggestions)
@ -38,9 +43,18 @@ export class IFQLPage extends PureComponent<Props, State> {
} catch (error) {
console.error('Could not get function suggestions: ', error)
}
try {
const ast = await getAST({url: links.ast, body: baseQuery})
this.setState({ast})
} catch (error) {
console.error('Could not parse AST', error)
}
}
public render() {
const {funcs} = this.state
return (
<div className="page">
<div className="page-header">
@ -52,12 +66,33 @@ export class IFQLPage extends PureComponent<Props, State> {
</div>
<div className="page-contents">
<div className="container-fluid">
<TimeMachine funcs={this.state.funcs} ast={ast} />
<TimeMachine
funcs={funcs}
nodes={this.nodes}
onAddNode={this.handleAddNode}
/>
</div>
</div>
</div>
)
}
private handleAddNode = (name: string) => {
console.log(name)
// Do a flip
}
private get nodes() {
const {ast} = this.state
if (!ast) {
return []
}
const walker = new Walker(ast)
return walker.functions
}
}
const mapStateToProps = ({links}) => {

View File

@ -5,6 +5,8 @@ import {connect} from 'react-redux'
import Authorized, {ADMIN_ROLE} from 'src/auth/Authorized'
import UserNavBlock from 'src/side_nav/components/UserNavBlock'
import FeatureFlag from 'src/shared/components/FeatureFlag'
import {
NavBlock,
NavHeader,
@ -139,6 +141,15 @@ class SideNav extends PureComponent<Props> {
sourcePrefix={sourcePrefix}
/>
) : null}
<FeatureFlag name="time-machine">
<NavBlock
icon="cog-thick"
link={`${sourcePrefix}/ifql`}
location={location}
>
<NavHeader link={`${sourcePrefix}/ifql`} title="IFQL Builder" />
</NavBlock>
</FeatureFlag>
</nav>
)
}

View File

@ -1,15 +1,16 @@
import React from 'react'
import {shallow} from 'enzyme'
import {FuncsButton} from 'src/ifql/components/FuncsButton'
import {FuncSelector} from 'src/ifql/components/FuncSelector'
import DropdownInput from 'src/shared/components/DropdownInput'
const setup = (override = {}) => {
const props = {
funcs: ['f1', 'f2'],
onAddNode: () => {},
...override,
}
const wrapper = shallow(<FuncsButton {...props} />)
const wrapper = shallow(<FuncSelector {...props} />)
return {
wrapper,