diff --git a/ui/src/ifql/ast/walker.ts b/ui/src/ifql/ast/walker.ts
index 7fa2355f89..ffb1f66e20 100644
--- a/ui/src/ifql/ast/walker.ts
+++ b/ui/src/ifql/ast/walker.ts
@@ -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
())
+ 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))
diff --git a/ui/src/ifql/components/Filter.tsx b/ui/src/ifql/components/Filter.tsx
new file mode 100644
index 0000000000..52020225ec
--- /dev/null
+++ b/ui/src/ifql/components/Filter.tsx
@@ -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 {
+ 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 (
+
+
+ {nodes.map((n, i) => {
+ return
{n.source}
+ })}
+
+ )
+ }
+}
+
+const mapStateToProps = ({links}) => {
+ return {links: links.ifql}
+}
+
+export default connect(mapStateToProps, null)(Filter)
diff --git a/ui/src/ifql/components/FuncArg.tsx b/ui/src/ifql/components/FuncArg.tsx
index 9a9555941a..47d5af353f 100644
--- a/ui/src/ifql/components/FuncArg.tsx
+++ b/ui/src/ifql/components/FuncArg.tsx
@@ -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 {
)
}
+ if (funcName === funcNames.FILTER) {
+ return (
+
+ )
+ }
+
switch (type) {
case argTypes.STRING:
case argTypes.DURATION:
diff --git a/ui/src/ifql/constants/funcNames.ts b/ui/src/ifql/constants/funcNames.ts
index 0f6fba6d6b..9fe675c068 100644
--- a/ui/src/ifql/constants/funcNames.ts
+++ b/ui/src/ifql/constants/funcNames.ts
@@ -1 +1,2 @@
export const FROM = 'from'
+export const FILTER = 'filter'
diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx
index 206a12c73e..7316444c02 100644
--- a/ui/src/ifql/containers/IFQLPage.tsx
+++ b/ui/src/ifql/containers/IFQLPage.tsx
@@ -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 {
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")`,
}
}
diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts
index e84e4d2c07..a3a1eaf991 100644
--- a/ui/src/types/ifql.ts
+++ b/ui/src/types/ifql.ts
@@ -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
+}
diff --git a/ui/test/ifql/components/Filter.test.tsx b/ui/test/ifql/components/Filter.test.tsx
new file mode 100644
index 0000000000..2330f535bf
--- /dev/null
+++ b/ui/test/ifql/components/Filter.test.tsx
@@ -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()
+
+ return {
+ wrapper,
+ props,
+ }
+}
+
+describe('IFQL.Components.Filter', () => {
+ describe('rendering', () => {
+ it('renders without errors', () => {
+ const {wrapper} = setup()
+ expect(wrapper.exists()).toBe(true)
+ })
+ })
+})