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) + }) + }) +})