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
parent
22fc9fb263
commit
5353c0bb57
|
@ -6,6 +6,7 @@ import (
|
|||
)
|
||||
|
||||
type getIFQLLinksResponse struct {
|
||||
AST string `json:"ast"`
|
||||
Self string `json:"self"`
|
||||
Suggestions string `json:"suggestions"`
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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', '')),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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}) => {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
Loading…
Reference in New Issue