Populate Filter funciton from ast

pull/10616/head
Andrew Watkins 2018-05-08 14:21:12 -07:00
parent 7bc346a22e
commit acc84cccc4
7 changed files with 204 additions and 50 deletions

View File

@ -1,6 +1,11 @@
// Texas Ranger
import _ from 'lodash'
import {FlatBody, Func} from 'src/types/ifql'
import {
Func,
FlatBody,
BinaryExpressionNode,
MemberExpressionNode,
} from 'src/types/ifql'
interface Expression {
argument: object
@ -29,6 +34,8 @@ interface AST {
body: Body[]
}
type InOrderNode = BinaryExpressionNode | MemberExpressionNode
export default class Walker {
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) {
const {location} = variable
const declarations = variable.declarations.map(d => {
@ -63,7 +115,7 @@ export default class Walker {
name,
type,
params: this.params(init.params),
body: this.binaryExpressionInOrder(init.body),
body: this.inOrder(init.body),
source: init.location.source,
}
}
@ -86,46 +138,6 @@ export default class Walker {
}
// 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 {
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 {ErrorHandling} from 'src/shared/decorators/errors'
import From from 'src/ifql/components/From'
import Filter from 'src/ifql/components/Filter'
import {funcNames, argTypes} from 'src/ifql/constants'
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) {
case argTypes.STRING:
case argTypes.DURATION:

View File

@ -1 +1,2 @@
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 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 {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 {ErrorHandling} from 'src/shared/decorators/errors'
interface Links {
self: string
suggestions: string
ast: string
}
interface Props {
links: Links
}
@ -44,7 +38,10 @@ export class IFQLPage extends PureComponent<Props, State> {
body: [],
ast: null,
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
generate?: boolean
}
// 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 {
type: string
source: string
@ -75,3 +97,9 @@ export interface Suggestion {
[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)
})
})
})