diff --git a/ui/src/ifql/ast/walker.ts b/ui/src/ifql/ast/walker.ts index 1ae9dc598..332a2a078 100644 --- a/ui/src/ifql/ast/walker.ts +++ b/ui/src/ifql/ast/walker.ts @@ -1,5 +1,6 @@ // Texas Ranger import _ from 'lodash' +import {FlatBody, Func} from 'src/types/ifql' interface Expression { argument: object @@ -19,14 +20,9 @@ interface Body { } interface FlatExpression { + type: string source: string - funcs: FuncNode[] -} - -interface FuncNode { - name: string - arguments: any[] - source: string + funcs: Func[] } interface AST { @@ -44,7 +40,7 @@ export default class Walker { return this.buildFuncNodes(this.walk(this.baseExpression)) } - public get stuff() { + public get body(): FlatBody[] { const body = _.get(this.ast, 'body', new Array()) return body.map(b => { if (b.type.includes('Expression')) { @@ -74,6 +70,7 @@ export default class Walker { const funcs = this.buildFuncNodes(this.walk(expression)) return { + type: expression.type, source: location.source, funcs, } @@ -86,6 +83,7 @@ export default class Walker { const funcs = this.buildFuncNodes(this.walk(expression)) return { + type: expression.type, source: location.source, funcs, } @@ -122,11 +120,11 @@ export default class Walker { return [{name, args, source}] } - private buildFuncNodes = (nodes): FuncNode[] => { + private buildFuncNodes = (nodes): Func[] => { return nodes.map(({name, args, source}) => { return { name, - arguments: this.reduceArgs(args), + args: this.reduceArgs(args), source, } }) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx new file mode 100644 index 000000000..a0ca545bd --- /dev/null +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -0,0 +1,101 @@ +import React, {PureComponent} from 'react' +import _ from 'lodash' + +import FuncSelector from 'src/ifql/components/FuncSelector' +import FuncNode from 'src/ifql/components/FuncNode' +import { + FlatBody, + OnAddNode, + Suggestion, + OnChangeArg, + OnDeleteFuncNode, +} from 'src/types/ifql' + +interface Props { + body: Body[] + onAddNode: OnAddNode + onChangeArg: OnChangeArg + onDeleteFuncNode: OnDeleteFuncNode + suggestions: Suggestion[] + onGenerateScript: () => void +} + +interface Body extends FlatBody { + id: string +} + +class BodyBuilder extends PureComponent { + public render() { + const { + body, + onAddNode, + onChangeArg, + onDeleteFuncNode, + onGenerateScript, + } = this.props + + const bodybuilder = body.map(b => { + if (b.declarations.length) { + return b.declarations.map(d => { + if (d.funcs) { + return ( +
+

+ {d.name} + +

+ {d.funcs.map(func => ( + + ))} +
+ ) + } + + return
{b.source}
+ }) + } + + return ( +
+

+ Expression + +

+ {b.funcs.map(func => ( + + ))} +
+ ) + }) + + return _.flatten(bodybuilder) + } + + private get funcNames() { + return this.props.suggestions.map(f => f.name) + } +} + +export default BodyBuilder diff --git a/ui/src/ifql/components/FuncArgs.tsx b/ui/src/ifql/components/FuncArgs.tsx index ddc8c2345..95ecfbd8a 100644 --- a/ui/src/ifql/components/FuncArgs.tsx +++ b/ui/src/ifql/components/FuncArgs.tsx @@ -2,21 +2,7 @@ import React, {PureComponent} from 'react' import FuncArg from 'src/ifql/components/FuncArg' import {OnChangeArg} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' - -type Value = string | boolean - -interface Arg { - key: string - value: Value - type: string -} - -export interface Func { - name: string - args: Arg[] - source: string - id: string -} +import {Func} from 'src/types/ifql' interface Props { func: Func @@ -30,6 +16,10 @@ export default class FuncArgs extends PureComponent { public render() { const {expressionID, func, onChangeArg, onGenerateScript} = this.props + if (!func.args) { + debugger + } + return (
{func.args.map(({key, value, type}) => { diff --git a/ui/src/ifql/components/TimeMachine.tsx b/ui/src/ifql/components/TimeMachine.tsx index 68d9301b6..a66f06a67 100644 --- a/ui/src/ifql/components/TimeMachine.tsx +++ b/ui/src/ifql/components/TimeMachine.tsx @@ -1,28 +1,20 @@ import React, {PureComponent} from 'react' -import FuncSelector from 'src/ifql/components/FuncSelector' -import FuncNode from 'src/ifql/components/FuncNode' +import BodyBuilder from 'src/ifql/components/BodyBuilder' import TimeMachineEditor from 'src/ifql/components/TimeMachineEditor' -import {Func} from 'src/ifql/components/FuncArgs' -import {OnChangeArg, OnDeleteFuncNode, OnAddNode} from 'src/types/ifql' +import { + FlatBody, + Suggestion, + OnChangeArg, + OnDeleteFuncNode, + OnAddNode, +} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' -export interface Suggestion { - name: string - params: { - [key: string]: string - } -} - -interface Expression { - id: string - funcs: Func[] -} - interface Props { script: string suggestions: Suggestion[] - expressions: Expression[] + body: Body[] onSubmitScript: () => void onChangeScript: (script: string) => void onAddNode: OnAddNode @@ -31,18 +23,23 @@ interface Props { onGenerateScript: () => void } +interface Body extends FlatBody { + id: string +} + @ErrorHandling class TimeMachine extends PureComponent { public render() { const { + body, script, onAddNode, - expressions, onChangeArg, onChangeScript, onSubmitScript, onDeleteFuncNode, onGenerateScript, + suggestions, } = this.props return ( @@ -53,38 +50,18 @@ class TimeMachine extends PureComponent { onSubmitScript={onSubmitScript} />
- {expressions.map(({funcs, id}, i) => { - return ( -
-

- Expression {i} - -

- {funcs.map(func => ( - - ))} -
- ) - })} +
) } - - private get funcNames() { - return this.props.suggestions.map(f => f.name) - } } export default TimeMachine diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 7c471ca2c..b212cf42d 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -4,12 +4,13 @@ import {connect} from 'react-redux' import uuid from 'uuid' import _ from 'lodash' -import TimeMachine, {Suggestion} from 'src/ifql/components/TimeMachine' +import TimeMachine from 'src/ifql/components/TimeMachine' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import Walker from 'src/ifql/ast/walker' -import {Func} from 'src/ifql/components/FuncArgs' +import {Func, Suggestion, FlatBody} from 'src/types/ifql' import {InputArg} from 'src/types/ifql' +import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST} from 'src/ifql/apis' import * as argTypes from 'src/ifql/constants/argumentTypes' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -24,17 +25,15 @@ interface Props { links: Links } -interface State { - suggestions: Suggestion[] - expressions: Expression[] - ast: object - script: string +interface Body extends FlatBody { + id: string } -interface Expression { - id: string - funcs: Func[] - source: string +interface State { + body: Body[] + ast: object + script: string + suggestions: Suggestion[] } @ErrorHandling @@ -42,11 +41,11 @@ export class IFQLPage extends PureComponent { constructor(props) { super(props) this.state = { - suggestions: [], - expressions: [], + body: [], ast: null, + suggestions: [], script: - 'from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nfrom(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', + 'foo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nfrom(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', } } @@ -80,7 +79,7 @@ export class IFQLPage extends PureComponent {
{ this.getASTResponse(script) } - private expressions = (ast, suggestions): Expression[] => { - if (!ast) { - return [] - } - - const walker = new Walker(ast) - - const expressions = walker.expressions.map(({funcs, source}) => { - const id = uuid.v4() - return { - id, - funcs: this.functions(funcs, suggestions), - source, - } - }) - - return expressions - } - - private functions = (funcs, suggestions): Func[] => { - const functions = funcs.map(func => { - const {params, name} = suggestions.find(f => f.name === func.name) - - const args = Object.entries(params).map(([key, type]) => { - const value = _.get( - func.arguments.find(arg => arg.key === key), - 'value', - '' - ) - - return { - key, - value, - type, - } - }) - - return { - id: uuid.v4(), - source: func.source, - name, - args, - } - }) - - return functions - } - private getASTResponse = async (script: string) => { const {links} = this.props try { const ast = await getAST({url: links.ast, body: script}) - const expressions = this.expressions(ast, this.state.suggestions) - this.setState({ast, script, expressions}) + const body = bodyNodes(ast, this.state.suggestions) + this.setState({ast, script, body}) } catch (error) { console.error('Could not parse AST', error) } diff --git a/ui/src/ifql/helpers/index.ts b/ui/src/ifql/helpers/index.ts new file mode 100644 index 000000000..00354c52a --- /dev/null +++ b/ui/src/ifql/helpers/index.ts @@ -0,0 +1,72 @@ +import uuid from 'uuid' +import _ from 'lodash' +import Walker from 'src/ifql/ast/walker' +import {FlatBody, Func} from 'src/types/ifql' + +interface Body extends FlatBody { + id: string +} + +export const bodyNodes = (ast, suggestions): Body[] => { + if (!ast) { + return [] + } + + const walker = new Walker(ast) + + const body = walker.body.map(b => { + const {type} = b + const id = uuid.v4() + if (type.includes('Variable')) { + const declarations = b.declarations.map(d => { + if (!d.funcs) { + return {...d, id: uuid.v4()} + } + + return { + ...d, + id: uuid.v4(), + funcs: functions(d.funcs, suggestions), + } + }) + + return {...b, type, id, declarations} + } + + const {funcs, source} = b + + return { + id, + funcs: functions(funcs, suggestions), + declarations: [], + type, + source, + } + }) + + return body +} + +const functions = (funcs, suggestions): Func[] => { + const funcList = funcs.map(func => { + const {params, name} = suggestions.find(f => f.name === func.name) + const args = Object.entries(params).map(([key, type]) => { + const value = _.get(func.args.find(arg => arg.key === key), 'value', '') + + return { + key, + value, + type, + } + }) + + return { + id: uuid.v4(), + source: func.source, + name, + args, + } + }) + + return funcList +} diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index d6028dc1a..5de7708fa 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -9,3 +9,46 @@ export interface InputArg { value: string | boolean generate?: boolean } + +// Flattened AST +export interface FlatBody { + type: string + source: string + funcs?: Func[] + declarations?: FlatDeclaration[] +} + +export interface Func { + type: string + name: string + args: Arg[] + source: string + id: string +} + +type Value = string | boolean + +interface Arg { + key: string + value: Value + type: string +} + +interface FlatExpression { + id: string + funcs?: Func[] +} + +interface FlatDeclaration extends FlatExpression { + name: string + value: string + type: string +} + +// Semantic Graph list of available functions for ifql queries +export interface Suggestion { + name: string + params: { + [key: string]: string + } +} diff --git a/ui/test/ifql/ast/varsAndExpressions.ts b/ui/test/ifql/ast/varsAndExpressions.ts new file mode 100644 index 000000000..b8c720fb0 --- /dev/null +++ b/ui/test/ifql/ast/varsAndExpressions.ts @@ -0,0 +1,352 @@ +export default { + type: 'Program', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 129}, + source: + 'literal = "foo"\n\ntele = from(db: "telegraf")\n\t|\u003e range(start: -15m)\n\nfrom(db: "telegraf")\n\t|\u003e filter() \n\t|\u003e range(start: -15m)\n\n', + }, + body: [ + { + type: 'VariableDeclaration', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 16}, + source: 'literal = "foo"', + }, + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + location: { + start: {line: 1, column: 1}, + end: {line: 1, column: 8}, + source: 'literal', + }, + name: 'literal', + }, + init: { + type: 'StringLiteral', + location: { + start: {line: 1, column: 11}, + end: {line: 1, column: 16}, + source: '"foo"', + }, + value: 'foo', + }, + }, + ], + }, + { + type: 'VariableDeclaration', + location: { + start: {line: 3, column: 1}, + end: {line: 3, column: 53}, + source: 'tele = from(db: "telegraf")\n\t|\u003e range(start: -15m)\n\n', + }, + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + location: { + start: {line: 3, column: 1}, + end: {line: 3, column: 5}, + source: 'tele', + }, + name: 'tele', + }, + init: { + type: 'PipeExpression', + location: { + start: {line: 4, column: 2}, + end: {line: 4, column: 23}, + source: '|\u003e range(start: -15m)', + }, + argument: { + type: 'CallExpression', + location: { + start: {line: 3, column: 8}, + end: {line: 3, column: 28}, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 3, column: 8}, + end: {line: 3, column: 12}, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: {line: 3, column: 13}, + end: {line: 3, column: 27}, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: {line: 3, column: 13}, + end: {line: 3, column: 27}, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: {line: 3, column: 13}, + end: {line: 3, column: 15}, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: {line: 3, column: 17}, + end: {line: 3, column: 27}, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + call: { + type: 'CallExpression', + location: { + start: {line: 4, column: 5}, + end: {line: 4, column: 23}, + source: 'range(start: -15m)', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 4, column: 5}, + end: {line: 4, column: 10}, + source: 'range', + }, + name: 'range', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: {line: 4, column: 11}, + end: {line: 4, column: 22}, + source: 'start: -15m', + }, + properties: [ + { + type: 'Property', + location: { + start: {line: 4, column: 11}, + end: {line: 4, column: 22}, + source: 'start: -15m', + }, + key: { + type: 'Identifier', + location: { + start: {line: 4, column: 11}, + end: {line: 4, column: 16}, + source: 'start', + }, + name: 'start', + }, + value: { + type: 'UnaryExpression', + location: { + start: {line: 4, column: 18}, + end: {line: 4, column: 22}, + source: '-15m', + }, + operator: '-', + argument: { + type: 'DurationLiteral', + location: { + start: {line: 4, column: 19}, + end: {line: 4, column: 22}, + source: '15m', + }, + value: '15m0s', + }, + }, + }, + ], + }, + ], + }, + }, + }, + ], + }, + { + type: 'ExpressionStatement', + location: { + start: {line: 6, column: 1}, + end: {line: 6, column: 60}, + source: + 'from(db: "telegraf")\n\t|\u003e filter() \n\t|\u003e range(start: -15m)\n\n', + }, + expression: { + type: 'PipeExpression', + location: { + start: {line: 8, column: 2}, + end: {line: 8, column: 23}, + source: '|\u003e range(start: -15m)', + }, + argument: { + type: 'PipeExpression', + location: { + start: {line: 7, column: 2}, + end: {line: 7, column: 13}, + source: '|\u003e filter()', + }, + argument: { + type: 'CallExpression', + location: { + start: {line: 6, column: 1}, + end: {line: 6, column: 21}, + source: 'from(db: "telegraf")', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 6, column: 1}, + end: {line: 6, column: 5}, + source: 'from', + }, + name: 'from', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: {line: 6, column: 6}, + end: {line: 6, column: 20}, + source: 'db: "telegraf"', + }, + properties: [ + { + type: 'Property', + location: { + start: {line: 6, column: 6}, + end: {line: 6, column: 20}, + source: 'db: "telegraf"', + }, + key: { + type: 'Identifier', + location: { + start: {line: 6, column: 6}, + end: {line: 6, column: 8}, + source: 'db', + }, + name: 'db', + }, + value: { + type: 'StringLiteral', + location: { + start: {line: 6, column: 10}, + end: {line: 6, column: 20}, + source: '"telegraf"', + }, + value: 'telegraf', + }, + }, + ], + }, + ], + }, + call: { + type: 'CallExpression', + location: { + start: {line: 7, column: 5}, + end: {line: 7, column: 13}, + source: 'filter()', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 7, column: 5}, + end: {line: 7, column: 11}, + source: 'filter', + }, + name: 'filter', + }, + }, + }, + call: { + type: 'CallExpression', + location: { + start: {line: 8, column: 5}, + end: {line: 8, column: 23}, + source: 'range(start: -15m)', + }, + callee: { + type: 'Identifier', + location: { + start: {line: 8, column: 5}, + end: {line: 8, column: 10}, + source: 'range', + }, + name: 'range', + }, + arguments: [ + { + type: 'ObjectExpression', + location: { + start: {line: 8, column: 11}, + end: {line: 8, column: 22}, + source: 'start: -15m', + }, + properties: [ + { + type: 'Property', + location: { + start: {line: 8, column: 11}, + end: {line: 8, column: 22}, + source: 'start: -15m', + }, + key: { + type: 'Identifier', + location: { + start: {line: 8, column: 11}, + end: {line: 8, column: 16}, + source: 'start', + }, + name: 'start', + }, + value: { + type: 'UnaryExpression', + location: { + start: {line: 8, column: 18}, + end: {line: 8, column: 22}, + source: '-15m', + }, + operator: '-', + argument: { + type: 'DurationLiteral', + location: { + start: {line: 8, column: 19}, + end: {line: 8, column: 22}, + source: '15m', + }, + value: '15m0s', + }, + }, + }, + ], + }, + ], + }, + }, + }, + ], +} diff --git a/ui/test/ifql/ast/walker.test.ts b/ui/test/ifql/ast/walker.test.ts index 87d6f45d0..5dfb7cf5e 100644 --- a/ui/test/ifql/ast/walker.test.ts +++ b/ui/test/ifql/ast/walker.test.ts @@ -7,16 +7,17 @@ describe('IFQL.AST.Walker', () => { describe('Walker#functions', () => { describe('simple example', () => { describe('a single expression', () => { - it('returns a flattened ordered list of from and its arguments', () => { + it('returns a flattened ordered list of from and its args', () => { const walker = new Walker(From) - expect(walker.stuff).toEqual([ + expect(walker.body).toEqual([ { + type: 'CallExpression', source: 'from(db: "telegraf")', funcs: [ { name: 'from', source: 'from(db: "telegraf")', - arguments: [ + args: [ { key: 'db', value: 'telegraf', @@ -32,7 +33,7 @@ describe('IFQL.AST.Walker', () => { describe('a single string literal variable', () => { it('returns the expected list', () => { const walker = new Walker(StringLiteral) - expect(walker.stuff).toEqual([ + expect(walker.body).toEqual([ { type: 'VariableDeclaration', source: 'bux = "im a var"', @@ -51,7 +52,7 @@ describe('IFQL.AST.Walker', () => { describe('a single expression variable', () => { it('returns the expected list', () => { const walker = new Walker(Expression) - expect(walker.stuff).toEqual([ + expect(walker.body).toEqual([ { type: 'VariableDeclaration', source: 'tele = from(db: "telegraf")', @@ -64,7 +65,7 @@ describe('IFQL.AST.Walker', () => { { name: 'from', source: 'from(db: "telegraf")', - arguments: [ + args: [ { key: 'db', value: 'telegraf', @@ -83,22 +84,23 @@ describe('IFQL.AST.Walker', () => { }) describe('complex example', () => { - it('returns a flattened ordered list of all funcs and their arguments', () => { + it('returns a flattened ordered list of all funcs and their args', () => { const walker = new Walker(Complex) - expect(walker.stuff).toEqual([ + expect(walker.body).toEqual([ { + type: 'PipeExpression', source: 'from(db: "telegraf") |> filter(fn: (r) => r["_measurement"] == "cpu") |> range(start: -1m)', funcs: [ { name: 'from', source: 'from(db: "telegraf")', - arguments: [{key: 'db', value: 'telegraf'}], + args: [{key: 'db', value: 'telegraf'}], }, { name: 'filter', source: '|> filter(fn: (r) => r["_measurement"] == "cpu")', - arguments: [ + args: [ { key: 'fn', value: '(r) => r["_measurement"] == "cpu"', @@ -108,7 +110,7 @@ describe('IFQL.AST.Walker', () => { { name: 'range', source: '|> range(start: -1m)', - arguments: [{key: 'start', value: '-1m'}], + args: [{key: 'start', value: '-1m'}], }, ], }, diff --git a/ui/test/ifql/helpers/bodyNodes.test.ts b/ui/test/ifql/helpers/bodyNodes.test.ts new file mode 100644 index 000000000..9e13ce3fd --- /dev/null +++ b/ui/test/ifql/helpers/bodyNodes.test.ts @@ -0,0 +1,148 @@ +import {bodyNodes} from 'src/ifql/helpers' +import suggestions from 'test/ifql/semantic_graph/suggestions' +import Variables from 'test/ifql/ast/variables' +import {Expression, StringLiteral} from 'test/ifql/ast/variable' +import From from 'test/ifql/ast/from' + +const id = expect.any(String) + +describe('IFQL.helpers', () => { + describe('bodyNodes', () => { + describe('bodyNodes for Expressions assigned to a variable', () => { + it('can parse an Expression assigned to a Variable', () => { + const actual = bodyNodes(Expression, suggestions) + const expected = [ + { + declarations: [ + { + funcs: [ + { + args: [{key: 'db', type: 'string', value: 'telegraf'}], + id, + name: 'from', + source: 'from(db: "telegraf")', + }, + ], + id, + name: 'tele', + source: 'tele = from(db: "telegraf")', + type: 'CallExpression', + }, + ], + id, + source: 'tele = from(db: "telegraf")', + type: 'VariableDeclaration', + }, + ] + + expect(actual).toEqual(expected) + }) + }) + + describe('bodyNodes for a Literal assigned to a Variable', () => { + it('can parse an Expression assigned to a Variable', () => { + const actual = bodyNodes(StringLiteral, suggestions) + const expected = [ + { + id, + source: 'bux = "im a var"', + type: 'VariableDeclaration', + declarations: [ + { + id, + name: 'bux', + type: 'StringLiteral', + value: 'im a var', + }, + ], + }, + ] + + expect(actual).toEqual(expected) + }) + }) + + describe('bodyNodes for an Expression', () => { + it('can parse an Expression into bodyNodes', () => { + const actual = bodyNodes(From, suggestions) + + const expected = [ + { + declarations: [], + funcs: [ + { + args: [{key: 'db', type: 'string', value: 'telegraf'}], + id, + name: 'from', + source: 'from(db: "telegraf")', + }, + ], + id, + source: 'from(db: "telegraf")', + type: 'CallExpression', + }, + ] + + expect(actual).toEqual(expected) + }) + }) + }) + + describe('multiple bodyNodes', () => { + it('can parse variables and expressions together', () => { + const actual = bodyNodes(Variables, suggestions) + const expected = [ + { + declarations: [ + { + id, + name: 'bux', + type: 'StringLiteral', + value: 'ASDFASDFASDF', + }, + ], + id, + source: 'bux = "ASDFASDFASDF"', + type: 'VariableDeclaration', + }, + { + declarations: [ + { + funcs: [ + { + args: [{key: 'db', type: 'string', value: 'foo'}], + id, + name: 'from', + source: 'from(db: "foo")', + }, + ], + id, + name: 'foo', + source: 'foo = from(db: "foo")', + type: 'CallExpression', + }, + ], + id, + source: 'foo = from(db: "foo")', + type: 'VariableDeclaration', + }, + { + declarations: [], + funcs: [ + { + args: [{key: 'db', type: 'string', value: 'bux'}], + id, + name: 'from', + source: 'from(db: bux)', + }, + ], + id, + source: 'from(db: bux)', + type: 'CallExpression', + }, + ] + + expect(actual).toEqual(expected) + }) + }) +}) diff --git a/ui/test/ifql/semantic_graph/suggestions.ts b/ui/test/ifql/semantic_graph/suggestions.ts new file mode 100644 index 000000000..f0ad0999f --- /dev/null +++ b/ui/test/ifql/semantic_graph/suggestions.ts @@ -0,0 +1,93 @@ +export default [ + { + name: '_highestOrLowest', + params: { + _sortLimit: 'invalid', + by: 'invalid', + cols: 'array', + n: 'invalid', + reducer: 'function', + }, + }, + { + name: '_sortLimit', + params: {cols: 'array', desc: 'invalid', n: 'invalid'}, + }, + {name: 'bottom', params: {cols: 'array', n: 'invalid'}}, + {name: 'count', params: {}}, + { + name: 'cov', + params: {on: 'invalid', pearsonr: 'bool', x: 'invalid', y: 'invalid'}, + }, + {name: 'covariance', params: {pearsonr: 'bool'}}, + {name: 'derivative', params: {nonNegative: 'bool', unit: 'duration'}}, + {name: 'difference', params: {nonNegative: 'bool'}}, + {name: 'distinct', params: {column: 'string'}}, + {name: 'filter', params: {fn: 'function'}}, + {name: 'first', params: {column: 'string', useRowTime: 'bool'}}, + {name: 'from', params: {db: 'string'}}, + {name: 'group', params: {by: 'array', except: 'array', keep: 'array'}}, + { + name: 'highestAverage', + params: {by: 'invalid', cols: 'array', n: 'invalid'}, + }, + { + name: 'highestCurrent', + params: {by: 'invalid', cols: 'array', n: 'invalid'}, + }, + {name: 'highestMax', params: {by: 'invalid', cols: 'array', n: 'invalid'}}, + {name: 'integral', params: {unit: 'duration'}}, + {name: 'join', params: {}}, + {name: 'last', params: {column: 'string', useRowTime: 'bool'}}, + {name: 'limit', params: {}}, + { + name: 'lowestAverage', + params: {by: 'invalid', cols: 'array', n: 'invalid'}, + }, + { + name: 'lowestCurrent', + params: {by: 'invalid', cols: 'array', n: 'invalid'}, + }, + {name: 'lowestMin', params: {by: 'invalid', cols: 'array', n: 'invalid'}}, + {name: 'map', params: {fn: 'function'}}, + {name: 'max', params: {column: 'string', useRowTime: 'bool'}}, + {name: 'mean', params: {}}, + {name: 'median', params: {compression: 'float', exact: 'bool'}}, + {name: 'min', params: {column: 'string', useRowTime: 'bool'}}, + {name: 'pearsonr', params: {on: 'invalid', x: 'invalid', y: 'invalid'}}, + {name: 'percentile', params: {p: 'float'}}, + {name: 'range', params: {start: 'time', stop: 'time'}}, + {name: 'sample', params: {column: 'string', useRowTime: 'bool'}}, + {name: 'set', params: {key: 'string', value: 'string'}}, + {name: 'shift', params: {shift: 'duration'}}, + {name: 'skew', params: {}}, + {name: 'sort', params: {cols: 'array'}}, + {name: 'spread', params: {}}, + {name: 'stateCount', params: {fn: 'invalid', label: 'string'}}, + { + name: 'stateDuration', + params: {fn: 'invalid', label: 'string', unit: 'duration'}, + }, + { + name: 'stateTracking', + params: { + countLabel: 'string', + durationLabel: 'string', + durationUnit: 'duration', + fn: 'function', + }, + }, + {name: 'stddev', params: {}}, + {name: 'sum', params: {}}, + {name: 'top', params: {cols: 'array', n: 'invalid'}}, + { + name: 'window', + params: { + every: 'duration', + period: 'duration', + round: 'duration', + start: 'time', + }, + }, + {name: 'yield', params: {name: 'string'}}, +]