diff --git a/ui/src/ifql/ast/walker.ts b/ui/src/ifql/ast/walker.ts new file mode 100644 index 000000000..e027425fb --- /dev/null +++ b/ui/src/ifql/ast/walker.ts @@ -0,0 +1,63 @@ +// Texas Ranger +import {get} from 'lodash' + +interface Expression { + expression: object +} + +interface AST { + body: Expression[] +} + +export default class Walker { + private ast: AST + + constructor(ast) { + this.ast = ast + } + + public get functions() { + return this.buildFuncNodes(this.walk(this.baseExpression)) + } + + private reduceArgs = args => { + return args.reduce( + (acc, arg) => [...acc, ...this.getProperties(arg.properties)], + [] + ) + } + + private walk = currentNode => { + let name + let args + if (currentNode.call) { + name = currentNode.call.callee.name + args = currentNode.call.arguments + return [...this.walk(currentNode.argument), {name, args}] + } + + name = currentNode.callee.name + args = currentNode.arguments + return [{name, args}] + } + + private buildFuncNodes = nodes => { + return nodes.map(node => { + return { + name: node.name, + arguments: this.reduceArgs(node.args), + } + }) + } + + private getProperties = props => { + return props.map(prop => ({ + name: prop.key.name, + value: get(prop, 'value.value', get(prop, 'value.location.source', '')), + })) + } + + private get baseExpression() { + return this.ast.body[0].expression + } +} diff --git a/ui/src/ifql/components/From.tsx b/ui/src/ifql/components/From.tsx new file mode 100644 index 000000000..ccd2588ad --- /dev/null +++ b/ui/src/ifql/components/From.tsx @@ -0,0 +1,99 @@ +import React, {PureComponent} from 'react' + +const obj = { + type: 'CallExpression', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 5, + }, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 20, + }, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], +} + +interface FromNode {} + +interface Props {} + +class From extends PureComponent {} diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 78f893362..75241046f 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -3,6 +3,7 @@ import FuncsButton from 'src/ifql/components/FuncsButton' interface Props { funcs: string[] + ast: object } const TimeMachine: SFC = ({funcs}) => { diff --git a/ui/src/ifql/constants/index.ts b/ui/src/ifql/constants/index.ts new file mode 100644 index 000000000..e542d8b43 --- /dev/null +++ b/ui/src/ifql/constants/index.ts @@ -0,0 +1,155 @@ +export const ast = { + type: 'File', + start: 0, + end: 22, + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + program: { + type: 'Program', + start: 0, + end: 22, + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + sourceType: 'module', + body: [ + { + type: 'ExpressionStatement', + start: 0, + end: 22, + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + expression: { + type: 'CallExpression', + start: 0, + end: 22, + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 22, + }, + }, + callee: { + type: 'Identifier', + start: 0, + end: 4, + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 4, + }, + identifierName: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + start: 5, + end: 21, + loc: { + start: { + line: 1, + column: 5, + }, + end: { + line: 1, + column: 21, + }, + }, + properties: [ + { + type: 'ObjectProperty', + start: 6, + end: 20, + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + }, + method: false, + shorthand: false, + computed: false, + key: { + type: 'Identifier', + start: 6, + end: 8, + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + identifierName: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + start: 10, + end: 20, + loc: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 20, + }, + }, + extra: { + rawValue: 'telegraf', + raw: 'telegraf', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + }, + ], + directives: [], + }, +} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 298ab8f27..37152aadf 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -5,6 +5,7 @@ import {connect} from 'react-redux' import TimeMachine from 'src/ifql/components/TimeMachine' import {getSuggestions} from 'src/ifql/apis' +import {ast} from 'src/ifql/constants' interface Links { self: string @@ -51,7 +52,7 @@ export class IFQLPage extends PureComponent {
- +
diff --git a/ui/test/ifql/ast/complex.ts b/ui/test/ifql/ast/complex.ts new file mode 100644 index 000000000..18f8f1bbe --- /dev/null +++ b/ui/test/ifql/ast/complex.ts @@ -0,0 +1,453 @@ +export default { + type: 'Program', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 91, + }, + source: + 'from(db: "telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu") |> range(start: -1m)', + }, + body: [ + { + type: 'ExpressionStatement', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 91, + }, + source: + 'from(db: "telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu") |> range(start: -1m)', + }, + expression: { + type: 'PipeExpression', + location: { + start: { + line: 1, + column: 71, + }, + end: { + line: 1, + column: 91, + }, + source: '|> range(start: -1m)', + }, + argument: { + type: 'PipeExpression', + location: { + start: { + line: 1, + column: 22, + }, + end: { + line: 1, + column: 70, + }, + source: '|> filter(fn: (r) => r["_measurement"] == "cpu")', + }, + argument: { + type: 'CallExpression', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 5, + }, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 20, + }, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + call: { + type: 'CallExpression', + location: { + start: { + line: 1, + column: 25, + }, + end: { + line: 1, + column: 70, + }, + source: 'filter(fn: (r) => r["_measurement"] == "cpu")', + }, + callee: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 25, + }, + end: { + line: 1, + column: 31, + }, + source: 'filter', + }, + name: 'filter', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: { + line: 1, + column: 32, + }, + end: { + line: 1, + column: 69, + }, + source: 'fn: (r) => r["_measurement"] == "cpu"', + }, + properties: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 32, + }, + end: { + line: 1, + column: 69, + }, + source: 'fn: (r) => r["_measurement"] == "cpu"', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 32, + }, + end: { + line: 1, + column: 34, + }, + source: 'fn', + }, + name: 'fn', + }, + value: { + type: 'ArrowFunctionExpression', + location: { + start: { + line: 1, + column: 36, + }, + end: { + line: 1, + column: 69, + }, + source: '(r) => r["_measurement"] == "cpu"', + }, + params: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 37, + }, + end: { + line: 1, + column: 38, + }, + source: 'r', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 37, + }, + end: { + line: 1, + column: 38, + }, + source: 'r', + }, + name: 'r', + }, + value: null, + }, + ], + body: { + type: 'BinaryExpression', + location: { + start: { + line: 1, + column: 43, + }, + end: { + line: 1, + column: 69, + }, + source: 'r["_measurement"] == "cpu"', + }, + operator: '==', + left: { + type: 'MemberExpression', + location: { + start: { + line: 1, + column: 43, + }, + end: { + line: 1, + column: 61, + }, + source: 'r["_measurement"] ', + }, + object: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 43, + }, + end: { + line: 1, + column: 44, + }, + source: 'r', + }, + name: 'r', + }, + property: { + type: 'StringLiteral', + location: { + start: { + line: 1, + column: 45, + }, + end: { + line: 1, + column: 59, + }, + source: '"_measurement"', + }, + value: '_measurement', + }, + }, + right: { + type: 'StringLiteral', + location: { + start: { + line: 1, + column: 64, + }, + end: { + line: 1, + column: 69, + }, + source: '"cpu"', + }, + value: 'cpu', + }, + }, + }, + }, + ], + }, + ], + }, + }, + call: { + type: 'CallExpression', + location: { + start: { + line: 1, + column: 74, + }, + end: { + line: 1, + column: 91, + }, + source: 'range(start: -1m)', + }, + callee: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 74, + }, + end: { + line: 1, + column: 79, + }, + source: 'range', + }, + name: 'range', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: { + line: 1, + column: 80, + }, + end: { + line: 1, + column: 90, + }, + source: 'start: -1m', + }, + properties: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 80, + }, + end: { + line: 1, + column: 90, + }, + source: 'start: -1m', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 80, + }, + end: { + line: 1, + column: 85, + }, + source: 'start', + }, + name: 'start', + }, + value: { + type: 'UnaryExpression', + location: { + start: { + line: 1, + column: 87, + }, + end: { + line: 1, + column: 90, + }, + source: '-1m', + }, + operator: '-', + argument: { + type: 'DurationLiteral', + location: { + start: { + line: 1, + column: 88, + }, + end: { + line: 1, + column: 90, + }, + source: '1m', + }, + value: '1m0s', + }, + }, + }, + ], + }, + ], + }, + }, + }, + ], +} diff --git a/ui/test/ifql/ast/from.ts b/ui/test/ifql/ast/from.ts new file mode 100644 index 000000000..b9cb46ff3 --- /dev/null +++ b/ui/test/ifql/ast/from.ts @@ -0,0 +1,121 @@ +export default { + type: 'Program', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + source: 'from(db: "telegraf")', + }, + body: [ + { + type: 'ExpressionStatement', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + source: 'from(db: "telegraf")', + }, + expression: { + type: 'CallExpression', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 21, + }, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: 5, + }, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 20, + }, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 8, + }, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: { + line: 1, + column: 10, + }, + end: { + line: 1, + column: 20, + }, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + }, + ], +} diff --git a/ui/test/ifql/ast/walker.test.ts b/ui/test/ifql/ast/walker.test.ts new file mode 100644 index 000000000..535023d6b --- /dev/null +++ b/ui/test/ifql/ast/walker.test.ts @@ -0,0 +1,49 @@ +import Walker from 'src/ifql/ast/walker' +import From from 'test/ifql/ast/from' +import Complex from 'test/ifql/ast/complex' + +describe('IFQL.AST.Walker', () => { + describe('Walker#functions', () => { + describe('simple example', () => { + it('returns a flattened ordered list of from and its arguments', () => { + const walker = new Walker(From) + expect(walker.functions).toEqual([ + { + name: 'from', + arguments: [ + { + name: 'db', + value: 'telegraf', + }, + ], + }, + ]) + }) + }) + + describe('complex example', () => { + it('returns a flattened ordered list of all funcs and their arguments', () => { + const walker = new Walker(Complex) + expect(walker.functions).toEqual([ + { + name: 'from', + arguments: [{name: 'db', value: 'telegraf'}], + }, + { + name: 'filter', + arguments: [ + { + name: 'fn', + value: '(r) => r["_measurement"] == "cpu"', + }, + ], + }, + { + name: 'range', + arguments: [{name: 'start', value: '-1m'}], + }, + ]) + }) + }) + }) +})