Populate Filter funciton from ast

pull/3421/head
Andrew Watkins 2018-05-08 14:21:12 -07:00
parent f1c27da71e
commit 4988f0932b
7 changed files with 204 additions and 50 deletions

View File

@ -1,6 +1,11 @@
// Texas Ranger // Texas Ranger
import _ from 'lodash' import _ from 'lodash'
import {FlatBody, Func} from 'src/types/ifql' import {
Func,
FlatBody,
BinaryExpressionNode,
MemberExpressionNode,
} from 'src/types/ifql'
interface Expression { interface Expression {
argument: object argument: object
@ -29,6 +34,8 @@ interface AST {
body: Body[] body: Body[]
} }
type InOrderNode = BinaryExpressionNode | MemberExpressionNode
export default class Walker { export default class Walker {
private ast: AST private ast: AST
@ -51,6 +58,51 @@ export default class Walker {
}) })
} }
public get inOrderExpression(): InOrderNode[] {
const tree = _.get(this.ast, 'body.0.expression.body', new Array<Body>())
return this.inOrder(tree)
}
private inOrder = (node): InOrderNode[] => {
let results = []
if (node) {
results = [...results, ...this.inOrder(node.left)]
if (node.type === 'MemberExpression') {
const {location, object, property} = node
const {name} = object
const {value, type} = property
const {source} = location
results = [
...results,
{
source,
object: {name, type: object.type},
property: {value, type},
type: node.type,
},
]
}
if (node.operator) {
results = [...results, {type: 'Operator', source: node.operator}]
}
if (node.name) {
results = [...results, {type: node.type, source: node.location.source}]
}
if (node.value) {
results = [...results, {type: node.type, source: node.location.source}]
}
results = [...results, ...this.inOrder(node.right)]
}
return results
}
private variable(variable) { private variable(variable) {
const {location} = variable const {location} = variable
const declarations = variable.declarations.map(d => { const declarations = variable.declarations.map(d => {
@ -63,7 +115,7 @@ export default class Walker {
name, name,
type, type,
params: this.params(init.params), params: this.params(init.params),
body: this.binaryExpressionInOrder(init.body), body: this.inOrder(init.body),
source: init.location.source, source: init.location.source,
} }
} }
@ -86,46 +138,6 @@ export default class Walker {
} }
// returns an in order flattening of a binary expression // returns an in order flattening of a binary expression
private inOrder = (node, result) => {
if (node) {
this.inOrder(node.left, result)
if (node.type === 'MemberExpression') {
const {location, object, property} = node
const {name} = object
const {value, type} = property
const {source} = location.source
result.push({
source,
object: {name, type: object.type},
property: {value, type},
type: node.type,
})
}
if (node.operator) {
result.push({type: 'Operator', source: node.operator})
}
if (node.name) {
result.push({type: node.type, source: node.location.source})
}
if (node.value) {
result.push({type: node.type, source: node.location.source})
}
this.inOrder(node.right, result)
}
}
private binaryExpressionInOrder = tree => {
const result = []
this.inOrder(tree, result)
return result
}
private expression(expression, location): FlatExpression { private expression(expression, location): FlatExpression {
const funcs = this.buildFuncNodes(this.walk(expression)) const funcs = this.buildFuncNodes(this.walk(expression))

View File

@ -0,0 +1,66 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {getAST} from 'src/ifql/apis'
import {
Links,
OnChangeArg,
BinaryExpressionNode,
MemberExpressionNode,
} from 'src/types/ifql'
import Walker from 'src/ifql/ast/walker'
interface Props {
argKey: string
funcID: string
bodyID: string
declarationID: string
value: string
onChangeArg: OnChangeArg
links: Links
}
type FilterNode = BinaryExpressionNode | MemberExpressionNode
interface State {
nodes: FilterNode[]
}
export class Filter extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
nodes: [],
}
}
public async componentDidMount() {
const {links, value} = this.props
try {
const ast = await getAST({url: links.ast, body: value})
const nodes = new Walker(ast).inOrderExpression
this.setState({nodes})
} catch (error) {
console.error('Could not parse AST', error)
}
}
public render() {
const {argKey} = this.props
const {nodes} = this.state
return (
<div className="func-arg">
<label className="func-arg--label">{argKey}</label>
{nodes.map((n, i) => {
return <div key={i}>{n.source}</div>
})}
</div>
)
}
}
const mapStateToProps = ({links}) => {
return {links: links.ifql}
}
export default connect(mapStateToProps, null)(Filter)

View File

@ -4,6 +4,7 @@ import FuncArgInput from 'src/ifql/components/FuncArgInput'
import FuncArgBool from 'src/ifql/components/FuncArgBool' import FuncArgBool from 'src/ifql/components/FuncArgBool'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import From from 'src/ifql/components/From' import From from 'src/ifql/components/From'
import Filter from 'src/ifql/components/Filter'
import {funcNames, argTypes} from 'src/ifql/constants' import {funcNames, argTypes} from 'src/ifql/constants'
import {OnChangeArg} from 'src/types/ifql' import {OnChangeArg} from 'src/types/ifql'
@ -48,6 +49,19 @@ class FuncArg extends PureComponent<Props> {
) )
} }
if (funcName === funcNames.FILTER) {
return (
<Filter
argKey={argKey}
funcID={funcID}
bodyID={bodyID}
value={this.value}
declarationID={declarationID}
onChangeArg={onChangeArg}
/>
)
}
switch (type) { switch (type) {
case argTypes.STRING: case argTypes.STRING:
case argTypes.DURATION: case argTypes.DURATION:

View File

@ -1 +1,2 @@
export const FROM = 'from' export const FROM = 'from'
export const FILTER = 'filter'

View File

@ -5,7 +5,7 @@ import _ from 'lodash'
import TimeMachine from 'src/ifql/components/TimeMachine' import TimeMachine from 'src/ifql/components/TimeMachine'
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
import {Suggestion, FlatBody} from 'src/types/ifql' import {Suggestion, FlatBody, Links} from 'src/types/ifql'
import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql'
import {bodyNodes} from 'src/ifql/helpers' import {bodyNodes} from 'src/ifql/helpers'
@ -13,12 +13,6 @@ import {getSuggestions, getAST} from 'src/ifql/apis'
import * as argTypes from 'src/ifql/constants/argumentTypes' import * as argTypes from 'src/ifql/constants/argumentTypes'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface Links {
self: string
suggestions: string
ast: string
}
interface Props { interface Props {
links: Links links: Links
} }
@ -44,7 +38,10 @@ export class IFQLPage extends PureComponent<Props, State> {
body: [], body: [],
ast: null, ast: null,
suggestions: [], suggestions: [],
script: 'addOne = (n) => n + 1\n\n', script: `from(db:"foo")
|> filter(fn: (r) => r["_measurement"]=="cpu" AND
r["_field"] == "usage_system" AND
r["service"] == "app-server")`,
} }
} }

View File

@ -33,7 +33,29 @@ export interface InputArg {
value: string | boolean value: string | boolean
generate?: boolean generate?: boolean
} }
// Flattened AST // Flattened AST
export interface BinaryExpressionNode {
source: string
type: string
}
interface ObjectNode {
name: string
type: string
}
interface PropertyNode {
value: string
type: string
}
export interface MemberExpressionNode {
source: string
object: ObjectNode
property: PropertyNode
}
export interface FlatBody { export interface FlatBody {
type: string type: string
source: string source: string
@ -75,3 +97,9 @@ export interface Suggestion {
[key: string]: string [key: string]: string
} }
} }
export interface Links {
self: string
suggestions: string
ast: string
}

View File

@ -0,0 +1,36 @@
import React from 'react'
import {shallow} from 'enzyme'
import {Filter} from 'src/ifql/components/Filter'
const setup = (override = {}) => {
const props = {
argKey: 'fn',
funcID: 'f1',
bodyID: 'b1',
declarationID: 'd1',
value: '(r) => r["measurement"] === "m1"',
onChangeArg: () => {},
links: {
self: '',
ast: '',
suggestions: '',
},
...override,
}
const wrapper = shallow(<Filter {...props} />)
return {
wrapper,
props,
}
}
describe('IFQL.Components.Filter', () => {
describe('rendering', () => {
it('renders without errors', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
})
})
})