From 178b366191bf9828a246c888746c5d8fb5b92de6 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Wed, 28 Mar 2018 14:57:55 -0700 Subject: [PATCH] Func item triggers a handler when it is clicked Co-authored-by: Brandon Farmer Co-authored-by: Andrew Watkins --- server/links.go | 1 + server/routes.go | 1 + ui/src/ifql/apis/index.ts | 22 +++++++++ ui/src/ifql/ast/walker.ts | 6 ++- ui/src/ifql/components/FuncList.tsx | 43 ++++++++++++++++++ ui/src/ifql/components/FuncListItem.tsx | 22 +++++++++ .../{FuncsButton.tsx => FuncSelector.tsx} | 45 +++++++++---------- ui/src/ifql/components/Node.tsx | 25 +++++++++++ ui/src/ifql/components/TimeMachine.tsx | 25 +++++++++-- ui/src/ifql/containers/IFQLPage.tsx | 43 ++++++++++++++++-- ui/src/side_nav/containers/SideNav.tsx | 11 +++++ ...sButton.test.tsx => FuncSelector.test.tsx} | 5 ++- 12 files changed, 215 insertions(+), 34 deletions(-) create mode 100644 ui/src/ifql/components/FuncList.tsx create mode 100644 ui/src/ifql/components/FuncListItem.tsx rename ui/src/ifql/components/{FuncsButton.tsx => FuncSelector.tsx} (58%) create mode 100644 ui/src/ifql/components/Node.tsx rename ui/test/ifql/components/{FuncsButton.test.tsx => FuncSelector.test.tsx} (94%) diff --git a/server/links.go b/server/links.go index ceda0db92..884ef0090 100644 --- a/server/links.go +++ b/server/links.go @@ -6,6 +6,7 @@ import ( ) type getIFQLLinksResponse struct { + AST string `json:"ast"` Self string `json:"self"` Suggestions string `json:"suggestions"` } diff --git a/server/routes.go b/server/routes.go index f275deea3..783de7abd 100644 --- a/server/routes.go +++ b/server/routes.go @@ -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", }, } diff --git a/ui/src/ifql/apis/index.ts b/ui/src/ifql/apis/index.ts index 041848b74..858d488d0 100644 --- a/ui/src/ifql/apis/index.ts +++ b/ui/src/ifql/apis/index.ts @@ -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 + } +} diff --git a/ui/src/ifql/ast/walker.ts b/ui/src/ifql/ast/walker.ts index e027425fb..f42c90fb4 100644 --- a/ui/src/ifql/ast/walker.ts +++ b/ui/src/ifql/ast/walker.ts @@ -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', '')), })) } diff --git a/ui/src/ifql/components/FuncList.tsx b/ui/src/ifql/components/FuncList.tsx new file mode 100644 index 000000000..098c123c9 --- /dev/null +++ b/ui/src/ifql/components/FuncList.tsx @@ -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) => void + onKeyDown: (e: KeyboardEvent) => void + onAddNode: (name: string) => void + funcs: string[] +} + +const FuncList: SFC = ({ + inputText, + isOpen, + onAddNode, + onKeyDown, + onInputChange, + funcs, +}) => { + return ( +
    + + + {isOpen && + funcs.map((func, i) => ( + + ))} + +
+ ) +} + +export default FuncList diff --git a/ui/src/ifql/components/FuncListItem.tsx b/ui/src/ifql/components/FuncListItem.tsx new file mode 100644 index 000000000..602624797 --- /dev/null +++ b/ui/src/ifql/components/FuncListItem.tsx @@ -0,0 +1,22 @@ +import React, {PureComponent} from 'react' + +interface Props { + name: string + onAddNode: (name: string) => void +} + +export default class FuncListItem extends PureComponent { + public render() { + const {name} = this.props + + return ( +
  • + {name} +
  • + ) + } + + private handleClick = () => { + this.props.onAddNode(this.props.name) + } +} diff --git a/ui/src/ifql/components/FuncsButton.tsx b/ui/src/ifql/components/FuncSelector.tsx similarity index 58% rename from ui/src/ifql/components/FuncsButton.tsx rename to ui/src/ifql/components/FuncSelector.tsx index 38586374f..bfdf036c7 100644 --- a/ui/src/ifql/components/FuncsButton.tsx +++ b/ui/src/ifql/components/FuncSelector.tsx @@ -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 { +export class FuncSelector extends PureComponent { constructor(props) { super(props) @@ -25,39 +24,39 @@ export class FuncsButton extends PureComponent { } public render() { + const {onAddNode} = this.props const {isOpen, inputText} = this.state return ( -
    +
    -
      - - - {isOpen && - this.availableFuncs.map((func, i) => ( -
    • - {func} -
    • - ))} -
      -
    +
    ) } + 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 { } } -export default FuncsButton +export default FuncSelector diff --git a/ui/src/ifql/components/Node.tsx b/ui/src/ifql/components/Node.tsx new file mode 100644 index 000000000..18dd0c673 --- /dev/null +++ b/ui/src/ifql/components/Node.tsx @@ -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 = ({node}) => { + return ( +
    +
    {node.name}
    +
    + ) +} + +export default Node diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 75241046f..7ec1b7b5a 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -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 = ({funcs}) => { - return +const TimeMachine: SFC = ({funcs, nodes, onAddNode}) => { + return ( +
    + {nodes.map((n, i) => )} + +
    + ) } export default TimeMachine diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 37152aadf..5540ad088 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -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 { @@ -25,11 +27,14 @@ export class IFQLPage extends PureComponent { 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 { } 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 (
    @@ -52,12 +66,33 @@ export class IFQLPage extends PureComponent {
    - +
    ) } + + 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}) => { diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index ca2d0f776..a45995cf2 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -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 { sourcePrefix={sourcePrefix} /> ) : null} + + + + + ) } diff --git a/ui/test/ifql/components/FuncsButton.test.tsx b/ui/test/ifql/components/FuncSelector.test.tsx similarity index 94% rename from ui/test/ifql/components/FuncsButton.test.tsx rename to ui/test/ifql/components/FuncSelector.test.tsx index 6882c84bd..0eb205bf2 100644 --- a/ui/test/ifql/components/FuncsButton.test.tsx +++ b/ui/test/ifql/components/FuncSelector.test.tsx @@ -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() + const wrapper = shallow() return { wrapper,