From edd8ea27da9b381aaedcf1414f594e71dfddb8e9 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 09:13:08 -0700 Subject: [PATCH 01/34] Change expressionID to bodyID --- ui/src/ifql/components/ExpressionNode.tsx | 4 ++-- ui/src/ifql/components/From.tsx | 6 +++--- ui/src/ifql/components/FuncArg.tsx | 10 +++++----- ui/src/ifql/components/FuncArgBool.tsx | 6 +++--- ui/src/ifql/components/FuncArgInput.tsx | 6 +++--- ui/src/ifql/components/FuncArgs.tsx | 6 +++--- ui/src/ifql/components/FuncNode.tsx | 10 +++++----- ui/src/ifql/components/FuncSelector.tsx | 4 ++-- ui/src/ifql/containers/IFQLPage.tsx | 15 ++++++--------- ui/src/types/ifql.ts | 6 +++--- ui/test/ifql/components/From.test.tsx | 2 +- ui/test/ifql/components/FuncArg.test.tsx | 2 +- ui/test/ifql/components/FuncSelector.test.tsx | 6 +++--- 13 files changed, 40 insertions(+), 43 deletions(-) diff --git a/ui/src/ifql/components/ExpressionNode.tsx b/ui/src/ifql/components/ExpressionNode.tsx index 5f6fabe916..16f559f1df 100644 --- a/ui/src/ifql/components/ExpressionNode.tsx +++ b/ui/src/ifql/components/ExpressionNode.tsx @@ -23,7 +23,7 @@ class ExpressionNode extends PureComponent {

@@ -32,7 +32,7 @@ class ExpressionNode extends PureComponent { { } private handleChooseDatabase = (item: DropdownItem): void => { - const {argKey, funcID, onChangeArg, expressionID} = this.props + const {argKey, funcID, onChangeArg, bodyID} = this.props onChangeArg({ funcID, key: argKey, value: item.text, - expressionID, + bodyID, generate: true, }) } diff --git a/ui/src/ifql/components/FuncArg.tsx b/ui/src/ifql/components/FuncArg.tsx index 091786a48a..18d57ab099 100644 --- a/ui/src/ifql/components/FuncArg.tsx +++ b/ui/src/ifql/components/FuncArg.tsx @@ -14,7 +14,7 @@ interface Props { argKey: string value: string | boolean type: string - expressionID: string + bodyID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -29,7 +29,7 @@ class FuncArg extends PureComponent { funcName, funcID, onChangeArg, - expressionID, + bodyID, onGenerateScript, } = this.props @@ -39,7 +39,7 @@ class FuncArg extends PureComponent { argKey={argKey} funcID={funcID} value={this.value} - expressionID={expressionID} + bodyID={bodyID} onChangeArg={onChangeArg} /> ) @@ -60,7 +60,7 @@ class FuncArg extends PureComponent { value={this.value} argKey={argKey} funcID={funcID} - expressionID={expressionID} + bodyID={bodyID} onChangeArg={onChangeArg} onGenerateScript={onGenerateScript} /> @@ -74,7 +74,7 @@ class FuncArg extends PureComponent { argKey={argKey} funcID={funcID} onChangeArg={onChangeArg} - expressionID={expressionID} + bodyID={bodyID} onGenerateScript={onGenerateScript} /> ) diff --git a/ui/src/ifql/components/FuncArgBool.tsx b/ui/src/ifql/components/FuncArgBool.tsx index a8ec37e8e0..0fe3fbd89b 100644 --- a/ui/src/ifql/components/FuncArgBool.tsx +++ b/ui/src/ifql/components/FuncArgBool.tsx @@ -7,7 +7,7 @@ interface Props { argKey: string value: boolean funcID: string - expressionID: string + bodyID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -23,8 +23,8 @@ class FuncArgBool extends PureComponent { } private handleToggle = (value: boolean): void => { - const {argKey, funcID, expressionID, onChangeArg} = this.props - onChangeArg({funcID, key: argKey, value, generate: true, expressionID}) + const {argKey, funcID, bodyID, onChangeArg} = this.props + onChangeArg({funcID, key: argKey, value, generate: true, bodyID}) } } diff --git a/ui/src/ifql/components/FuncArgInput.tsx b/ui/src/ifql/components/FuncArgInput.tsx index ea0070a80a..694b89b3d2 100644 --- a/ui/src/ifql/components/FuncArgInput.tsx +++ b/ui/src/ifql/components/FuncArgInput.tsx @@ -7,7 +7,7 @@ interface Props { argKey: string value: string type: string - expressionID: string + bodyID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -44,13 +44,13 @@ class FuncArgInput extends PureComponent { } private handleChange = (e: ChangeEvent) => { - const {funcID, argKey, expressionID} = this.props + const {funcID, argKey, bodyID} = this.props this.props.onChangeArg({ funcID, key: argKey, value: e.target.value, - expressionID, + bodyID, }) } } diff --git a/ui/src/ifql/components/FuncArgs.tsx b/ui/src/ifql/components/FuncArgs.tsx index c8be0ac3c9..910f3caa96 100644 --- a/ui/src/ifql/components/FuncArgs.tsx +++ b/ui/src/ifql/components/FuncArgs.tsx @@ -6,7 +6,7 @@ import {Func} from 'src/types/ifql' interface Props { func: Func - expressionID: string + bodyID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -14,7 +14,7 @@ interface Props { @ErrorHandling export default class FuncArgs extends PureComponent { public render() { - const {expressionID, func, onChangeArg, onGenerateScript} = this.props + const {bodyID, func, onChangeArg, onGenerateScript} = this.props return (
@@ -28,7 +28,7 @@ export default class FuncArgs extends PureComponent { funcID={func.id} funcName={func.name} onChangeArg={onChangeArg} - expressionID={expressionID} + bodyID={bodyID} onGenerateScript={onGenerateScript} /> ) diff --git a/ui/src/ifql/components/FuncNode.tsx b/ui/src/ifql/components/FuncNode.tsx index 7d83005cec..6bb8bbfcd2 100644 --- a/ui/src/ifql/components/FuncNode.tsx +++ b/ui/src/ifql/components/FuncNode.tsx @@ -5,8 +5,8 @@ import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { func: Func - expressionID: string - onDelete: (funcID: string, expressionID: string) => void + bodyID: string + onDelete: (funcID: string, bodyID: string) => void onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -25,7 +25,7 @@ export default class FuncNode extends PureComponent { } public render() { - const {expressionID, func, onChangeArg, onGenerateScript} = this.props + const {bodyID, func, onChangeArg, onGenerateScript} = this.props const {isOpen} = this.state return ( @@ -37,7 +37,7 @@ export default class FuncNode extends PureComponent { )} @@ -49,7 +49,7 @@ export default class FuncNode extends PureComponent { } private handleDelete = (): void => { - this.props.onDelete(this.props.func.id, this.props.expressionID) + this.props.onDelete(this.props.func.id, this.props.bodyID) } private handleClick = (e: MouseEvent): void => { diff --git a/ui/src/ifql/components/FuncSelector.tsx b/ui/src/ifql/components/FuncSelector.tsx index 8654f6ef1d..a3e2c2a821 100644 --- a/ui/src/ifql/components/FuncSelector.tsx +++ b/ui/src/ifql/components/FuncSelector.tsx @@ -14,7 +14,7 @@ interface State { interface Props { funcs: string[] - expressionID: string + bodyID: string onAddNode: OnAddNode } @@ -66,7 +66,7 @@ export class FuncSelector extends PureComponent { private handleAddNode = (name: string) => { this.handleCloseList() - this.props.onAddNode(name, this.props.expressionID) + this.props.onAddNode(name, this.props.bodyID) } private get availableFuncs() { diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index ba7008824b..49ed8e3a23 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -116,10 +116,10 @@ export class IFQLPage extends PureComponent { value, generate, funcID, - expressionID, + bodyID, }: InputArg): void => { const body = this.state.body.map(expression => { - if (expression.id !== expressionID) { + if (expression.id !== bodyID) { return expression } @@ -183,9 +183,9 @@ export class IFQLPage extends PureComponent { this.setState({script}) } - private handleAddNode = (name: string, expressionID: string): void => { + private handleAddNode = (name: string, bodyID: string): void => { const script = this.state.body.reduce((acc, expression) => { - if (expression.id === expressionID) { + if (expression.id === bodyID) { const {funcs} = expression return `${acc}${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n` } @@ -196,14 +196,11 @@ export class IFQLPage extends PureComponent { this.getASTResponse(script) } - private handleDeleteFuncNode = ( - funcID: string, - expressionID: string - ): void => { + private handleDeleteFuncNode = (funcID: string, bodyID: string): void => { // TODO: export this and test functionality const script = this.state.body .map((expression, expressionIndex) => { - if (expression.id !== expressionID) { + if (expression.id !== bodyID) { return expression.source } diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index 4cf3326e85..e8b725bffd 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -1,7 +1,7 @@ // function definitions -export type OnDeleteFuncNode = (funcID: string, expressionID: string) => void +export type OnDeleteFuncNode = (funcID: string, bodyID: string) => void export type OnChangeArg = (inputArg: InputArg) => void -export type OnAddNode = (expressionID: string, funcName: string) => void +export type OnAddNode = (bodyID: string, funcName: string) => void export type OnGenerateScript = (script: string) => void export type OnChangeScript = (script: string) => void export type OnSubmitScript = () => void @@ -17,7 +17,7 @@ export interface Handlers { export interface InputArg { funcID: string - expressionID: string + bodyID: string key: string value: string | boolean generate?: boolean diff --git a/ui/test/ifql/components/From.test.tsx b/ui/test/ifql/components/From.test.tsx index 4a8adad3d9..41466f527e 100644 --- a/ui/test/ifql/components/From.test.tsx +++ b/ui/test/ifql/components/From.test.tsx @@ -9,7 +9,7 @@ const setup = () => { funcID: '1', argKey: 'db', value: 'db1', - expressionID: '2', + bodyID: '2', onChangeArg: () => {}, } diff --git a/ui/test/ifql/components/FuncArg.test.tsx b/ui/test/ifql/components/FuncArg.test.tsx index 98949f4a84..65d5a5367e 100644 --- a/ui/test/ifql/components/FuncArg.test.tsx +++ b/ui/test/ifql/components/FuncArg.test.tsx @@ -5,7 +5,7 @@ import FuncArg from 'src/ifql/components/FuncArg' const setup = () => { const props = { funcID: '', - expressionID: '', + bodyID: '', funcName: '', argKey: '', value: '', diff --git a/ui/test/ifql/components/FuncSelector.test.tsx b/ui/test/ifql/components/FuncSelector.test.tsx index 8218876099..1ec41d1273 100644 --- a/ui/test/ifql/components/FuncSelector.test.tsx +++ b/ui/test/ifql/components/FuncSelector.test.tsx @@ -8,7 +8,7 @@ import FuncList from 'src/ifql/components/FuncList' const setup = (override = {}) => { const props = { funcs: ['count', 'range'], - expressionID: '1', + bodyID: '1', onAddNode: () => {}, ...override, } @@ -133,7 +133,7 @@ describe('IFQL.Components.FuncsButton', () => { const onAddNode = jest.fn() const {wrapper, props} = setup({onAddNode}) const [, func2] = props.funcs - const {expressionID} = props + const {bodyID} = props const dropdownButton = wrapper.find('button') dropdownButton.simulate('click') @@ -148,7 +148,7 @@ describe('IFQL.Components.FuncsButton', () => { input.simulate('keyDown', {key: 'ArrowDown'}) input.simulate('keyDown', {key: 'Enter'}) - expect(onAddNode).toHaveBeenCalledWith(func2, expressionID) + expect(onAddNode).toHaveBeenCalledWith(func2, bodyID) }) }) }) From 1b80d2a2ff96f2c8adac8087b21eae42361e536c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 09:33:32 -0700 Subject: [PATCH 02/34] Use correct ID in funcNode --- ui/src/ifql/components/BodyBuilder.tsx | 8 ++++---- ui/src/ifql/components/ExpressionNode.tsx | 8 ++++---- ui/src/ifql/components/FuncArgs.tsx | 2 +- ui/src/ifql/components/FuncNode.tsx | 2 +- ui/src/ifql/containers/IFQLPage.tsx | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index b1602438e7..0acd7e308c 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -22,8 +22,8 @@ class BodyBuilder extends PureComponent { if (d.funcs) { return ( @@ -36,10 +36,10 @@ class BodyBuilder extends PureComponent { return ( ) }) diff --git a/ui/src/ifql/components/ExpressionNode.tsx b/ui/src/ifql/components/ExpressionNode.tsx index 16f559f1df..a76f0c7e38 100644 --- a/ui/src/ifql/components/ExpressionNode.tsx +++ b/ui/src/ifql/components/ExpressionNode.tsx @@ -8,14 +8,14 @@ import {Func} from 'src/types/ifql' interface Props { funcNames: any[] - id: string + bodyID: string funcs: Func[] } // an Expression is a group of one or more functions class ExpressionNode extends PureComponent { public render() { - const {id, funcNames, funcs} = this.props + const {bodyID, funcNames, funcs} = this.props return ( {({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => { @@ -23,7 +23,7 @@ class ExpressionNode extends PureComponent {

@@ -32,7 +32,7 @@ class ExpressionNode extends PureComponent { { type={type} argKey={key} value={value} + bodyID={bodyID} funcID={func.id} funcName={func.name} onChangeArg={onChangeArg} - bodyID={bodyID} onGenerateScript={onGenerateScript} /> ) diff --git a/ui/src/ifql/components/FuncNode.tsx b/ui/src/ifql/components/FuncNode.tsx index 6bb8bbfcd2..47f049f6e6 100644 --- a/ui/src/ifql/components/FuncNode.tsx +++ b/ui/src/ifql/components/FuncNode.tsx @@ -36,8 +36,8 @@ export default class FuncNode extends PureComponent { {isOpen && ( )} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 49ed8e3a23..662e48d7d7 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -199,12 +199,12 @@ export class IFQLPage extends PureComponent { private handleDeleteFuncNode = (funcID: string, bodyID: string): void => { // TODO: export this and test functionality const script = this.state.body - .map((expression, expressionIndex) => { - if (expression.id !== bodyID) { - return expression.source + .map((body, bodyIndex) => { + if (body.id !== bodyID) { + return body.source } - const funcs = expression.funcs.filter(f => f.id !== funcID) + const funcs = body.funcs.filter(f => f.id !== funcID) const source = funcs.reduce((acc, f, i) => { if (i === 0) { return `${f.source}` @@ -213,7 +213,7 @@ export class IFQLPage extends PureComponent { return `${acc}\n\t${f.source}` }, '') - const isLast = expressionIndex === this.state.body.length - 1 + const isLast = bodyIndex === this.state.body.length - 1 if (isLast) { return `${source}` } From 29fe9d1119d3cbc772a96fa3b46a381b02f4e42d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 10:54:17 -0700 Subject: [PATCH 03/34] Fix delete to work with variable declarations --- ui/src/ifql/components/BodyBuilder.tsx | 3 +- ui/src/ifql/components/ExpressionNode.tsx | 4 +- ui/src/ifql/components/FuncNode.tsx | 13 ++++-- ui/src/ifql/containers/IFQLPage.tsx | 53 +++++++++++++++++------ ui/src/types/ifql.ts | 8 +++- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index 0acd7e308c..a6d8d6f647 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -22,8 +22,9 @@ class BodyBuilder extends PureComponent { if (d.funcs) { return ( diff --git a/ui/src/ifql/components/ExpressionNode.tsx b/ui/src/ifql/components/ExpressionNode.tsx index a76f0c7e38..2b57b2cfd4 100644 --- a/ui/src/ifql/components/ExpressionNode.tsx +++ b/ui/src/ifql/components/ExpressionNode.tsx @@ -10,12 +10,13 @@ interface Props { funcNames: any[] bodyID: string funcs: Func[] + declarationID?: string } // an Expression is a group of one or more functions class ExpressionNode extends PureComponent { public render() { - const {bodyID, funcNames, funcs} = this.props + const {declarationID, bodyID, funcNames, funcs} = this.props return ( {({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => { @@ -35,6 +36,7 @@ class ExpressionNode extends PureComponent { bodyID={bodyID} onChangeArg={onChangeArg} onDelete={onDeleteFuncNode} + declarationID={declarationID} onGenerateScript={onGenerateScript} /> ))} diff --git a/ui/src/ifql/components/FuncNode.tsx b/ui/src/ifql/components/FuncNode.tsx index 47f049f6e6..a8c52f78a5 100644 --- a/ui/src/ifql/components/FuncNode.tsx +++ b/ui/src/ifql/components/FuncNode.tsx @@ -1,12 +1,13 @@ import React, {PureComponent, MouseEvent} from 'react' import FuncArgs from 'src/ifql/components/FuncArgs' -import {OnChangeArg, Func} from 'src/types/ifql' +import {OnDeleteFuncNode, OnChangeArg, Func} from 'src/types/ifql' import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { func: Func bodyID: string - onDelete: (funcID: string, bodyID: string) => void + declarationID?: string + onDelete: OnDeleteFuncNode onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -17,6 +18,10 @@ interface State { @ErrorHandling export default class FuncNode extends PureComponent { + public static defaultProps: Partial = { + declarationID: '', + } + constructor(props) { super(props) this.state = { @@ -49,7 +54,9 @@ export default class FuncNode extends PureComponent { } private handleDelete = (): void => { - this.props.onDelete(this.props.func.id, this.props.bodyID) + const {func, bodyID, declarationID} = this.props + + this.props.onDelete({funcID: func.id, bodyID, declarationID}) } private handleClick = (e: MouseEvent): void => { diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 662e48d7d7..2b970f6100 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -5,7 +5,7 @@ import {connect} from 'react-redux' import TimeMachine from 'src/ifql/components/TimeMachine' import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts' import {Suggestion, FlatBody} from 'src/types/ifql' -import {InputArg, Handlers} from 'src/types/ifql' +import {InputArg, Handlers, DeleteFuncNodeArgs} from 'src/types/ifql' import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST} from 'src/ifql/apis' @@ -196,35 +196,60 @@ export class IFQLPage extends PureComponent { this.getASTResponse(script) } - private handleDeleteFuncNode = (funcID: string, bodyID: string): void => { - // TODO: export this and test functionality + private handleDeleteFuncNode = (ids: DeleteFuncNodeArgs): void => { + const {funcID, declarationID = '', bodyID} = ids + const script = this.state.body .map((body, bodyIndex) => { if (body.id !== bodyID) { return body.source } - const funcs = body.funcs.filter(f => f.id !== funcID) - const source = funcs.reduce((acc, f, i) => { - if (i === 0) { - return `${f.source}` + const isLast = bodyIndex === this.state.body.length - 1 + + if (declarationID) { + const declaration = body.declarations.find( + d => d.id === declarationID + ) + + if (!declaration) { + return } - return `${acc}\n\t${f.source}` - }, '') - - const isLast = bodyIndex === this.state.body.length - 1 - if (isLast) { - return `${source}` + const functions = declaration.funcs.filter(f => f.id !== funcID) + const s = this.funcsToSource(functions) + return `${declaration.name} = ${this.parseLastSource(s, isLast)}` } - return `${source}\n\n` + const funcs = body.funcs.filter(f => f.id !== funcID) + const source = this.funcsToSource(funcs) + return this.parseLastSource(source, isLast) }) .join('') this.getASTResponse(script) } + // formats the last line of a body string to include two new lines + private parseLastSource = (source: string, isLast: boolean): string => { + if (isLast) { + return `${source}` + } + + return `${source}\n\n` + } + + // funcsToSource takes a list of funtion nodes and returns an ifql script + private funcsToSource = (funcs): string => { + return funcs.reduce((acc, f, i) => { + if (i === 0) { + return `${f.source}` + } + + return `${acc}\n\t${f.source}` + }, '') + } + private getASTResponse = async (script: string) => { const {links} = this.props diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index e8b725bffd..289609efeb 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -1,5 +1,5 @@ // function definitions -export type OnDeleteFuncNode = (funcID: string, bodyID: string) => void +export type OnDeleteFuncNode = (ids: DeleteFuncNodeArgs) => void export type OnChangeArg = (inputArg: InputArg) => void export type OnAddNode = (bodyID: string, funcName: string) => void export type OnGenerateScript = (script: string) => void @@ -15,6 +15,12 @@ export interface Handlers { onGenerateScript: OnGenerateScript } +export interface DeleteFuncNodeArgs { + funcID: string + bodyID: string + declarationID?: string +} + export interface InputArg { funcID: string bodyID: string From 22c094aa302536264cf2cb91c0a787ff6108fb39 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Tue, 24 Apr 2018 11:46:48 -0700 Subject: [PATCH 04/34] Prevent dygraph from continously updating --- ui/src/shared/components/AutoRefresh.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js index e3fdce8b0f..63f39293dd 100644 --- a/ui/src/shared/components/AutoRefresh.js +++ b/ui/src/shared/components/AutoRefresh.js @@ -165,7 +165,9 @@ const AutoRefresh = ComposedComponent => { } setResolution = resolution => { - this.setState({resolution}) + if (resolution !== this.state.resolution) { + this.setState({resolution}) + } } render() { From 8ebd3a3cd924e91bd7553b06d3018dc0d7c8e87e Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Tue, 24 Apr 2018 13:18:24 -0700 Subject: [PATCH 05/34] Fix linting errors --- ui/src/shared/apis/index.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ui/src/shared/apis/index.js b/ui/src/shared/apis/index.js index 6c15229d54..3620097f82 100644 --- a/ui/src/shared/apis/index.js +++ b/ui/src/shared/apis/index.js @@ -185,11 +185,9 @@ export const testAlertOutput = async (kapacitor, outputName) => { export const getAllServices = async kapacitor => { try { - const {data: {services}} = await kapacitorProxy( - kapacitor, - 'GET', - '/kapacitor/v1/service-tests' - ) + const { + data: {services}, + } = await kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests') return services } catch (error) { console.error(error) From 57fa9e1c1630d53750605730f3f6500c930156e6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 13:36:59 -0700 Subject: [PATCH 06/34] Fix function argument editing --- ui/src/ifql/components/FuncArg.tsx | 11 ++-- ui/src/ifql/components/FuncArgInput.tsx | 4 +- ui/src/ifql/components/FuncArgs.tsx | 10 ++- ui/src/ifql/components/FuncNode.tsx | 9 ++- ui/src/ifql/containers/IFQLPage.tsx | 81 +++++++++++++++++------- ui/src/types/ifql.ts | 1 + ui/test/ifql/components/FuncArg.test.tsx | 1 + 7 files changed, 87 insertions(+), 30 deletions(-) diff --git a/ui/src/ifql/components/FuncArg.tsx b/ui/src/ifql/components/FuncArg.tsx index 18d57ab099..389f199946 100644 --- a/ui/src/ifql/components/FuncArg.tsx +++ b/ui/src/ifql/components/FuncArg.tsx @@ -15,6 +15,7 @@ interface Props { value: string | boolean type: string bodyID: string + declarationID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -26,10 +27,11 @@ class FuncArg extends PureComponent { argKey, value, type, - funcName, - funcID, - onChangeArg, bodyID, + funcID, + funcName, + onChangeArg, + declarationID, onGenerateScript, } = this.props @@ -62,6 +64,7 @@ class FuncArg extends PureComponent { funcID={funcID} bodyID={bodyID} onChangeArg={onChangeArg} + declarationID={declarationID} onGenerateScript={onGenerateScript} /> ) @@ -72,9 +75,9 @@ class FuncArg extends PureComponent { ) diff --git a/ui/src/ifql/components/FuncArgInput.tsx b/ui/src/ifql/components/FuncArgInput.tsx index 694b89b3d2..644e8f316e 100644 --- a/ui/src/ifql/components/FuncArgInput.tsx +++ b/ui/src/ifql/components/FuncArgInput.tsx @@ -8,6 +8,7 @@ interface Props { value: string type: string bodyID: string + declarationID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -44,12 +45,13 @@ class FuncArgInput extends PureComponent { } private handleChange = (e: ChangeEvent) => { - const {funcID, argKey, bodyID} = this.props + const {funcID, argKey, bodyID, declarationID} = this.props this.props.onChangeArg({ funcID, key: argKey, value: e.target.value, + declarationID, bodyID, }) } diff --git a/ui/src/ifql/components/FuncArgs.tsx b/ui/src/ifql/components/FuncArgs.tsx index 4a33df9c94..10c75d4cf1 100644 --- a/ui/src/ifql/components/FuncArgs.tsx +++ b/ui/src/ifql/components/FuncArgs.tsx @@ -8,13 +8,20 @@ interface Props { func: Func bodyID: string onChangeArg: OnChangeArg + declarationID: string onGenerateScript: () => void } @ErrorHandling export default class FuncArgs extends PureComponent { public render() { - const {bodyID, func, onChangeArg, onGenerateScript} = this.props + const { + func, + bodyID, + onChangeArg, + declarationID, + onGenerateScript, + } = this.props return (
@@ -29,6 +36,7 @@ export default class FuncArgs extends PureComponent { funcID={func.id} funcName={func.name} onChangeArg={onChangeArg} + declarationID={declarationID} onGenerateScript={onGenerateScript} /> ) diff --git a/ui/src/ifql/components/FuncNode.tsx b/ui/src/ifql/components/FuncNode.tsx index a8c52f78a5..343f6bd1bf 100644 --- a/ui/src/ifql/components/FuncNode.tsx +++ b/ui/src/ifql/components/FuncNode.tsx @@ -30,7 +30,13 @@ export default class FuncNode extends PureComponent { } public render() { - const {bodyID, func, onChangeArg, onGenerateScript} = this.props + const { + func, + bodyID, + onChangeArg, + declarationID, + onGenerateScript, + } = this.props const {isOpen} = this.state return ( @@ -43,6 +49,7 @@ export default class FuncNode extends PureComponent { func={func} bodyID={bodyID} onChangeArg={onChangeArg} + declarationID={declarationID} onGenerateScript={onGenerateScript} /> )} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 2b970f6100..f4b95b3abb 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -1,11 +1,12 @@ import React, {PureComponent} from 'react' import {connect} from 'react-redux' +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 {InputArg, Handlers, DeleteFuncNodeArgs} from 'src/types/ifql' +import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql' import {bodyNodes} from 'src/ifql/helpers' import {getSuggestions, getAST} from 'src/ifql/apis' @@ -108,7 +109,7 @@ export class IFQLPage extends PureComponent { } private handleGenerateScript = (): void => { - this.getASTResponse(this.expressionsToScript) + this.getASTResponse(this.bodyToScript) } private handleChangeArg = ({ @@ -116,30 +117,41 @@ export class IFQLPage extends PureComponent { value, generate, funcID, + declarationID = '', bodyID, }: InputArg): void => { - const body = this.state.body.map(expression => { - if (expression.id !== bodyID) { - return expression + const body = this.state.body.map(b => { + if (b.id !== bodyID) { + return b } - const funcs = expression.funcs.map(f => { - if (f.id !== funcID) { - return f - } - - const args = f.args.map(a => { - if (a.key === key) { - return {...a, value} + if (declarationID) { + const declarations = b.declarations.map(d => { + if (d.id !== declarationID) { + return d } - return a + const functions = this.editFuncArgs({ + funcs: d.funcs, + funcID, + key, + value, + }) + + return {...d, funcs: functions} }) - return {...f, args} + return {...b, declarations} + } + + const funcs = this.editFuncArgs({ + funcs: b.funcs, + funcID, + key, + value, }) - return {...expression, funcs} + return {...b, funcs} }) this.setState({body}, () => { @@ -149,9 +161,32 @@ export class IFQLPage extends PureComponent { }) } - private get expressionsToScript(): string { - return this.state.body.reduce((acc, expression) => { - return `${acc + this.funcsToScript(expression.funcs)}\n\n` + private editFuncArgs = ({funcs, funcID, key, value}): Func[] => { + return funcs.map(f => { + if (f.id !== funcID) { + return f + } + + const args = f.args.map(a => { + if (a.key === key) { + return {...a, value} + } + + return a + }) + + return {...f, args} + }) + } + + private get bodyToScript(): string { + return this.state.body.reduce((acc, b) => { + if (b.declarations.length) { + const funcs = _.get(b, 'declarations.0.funcs', []) + return `${acc}${this.funcsToScript(funcs)}\n\n` + } + + return `${acc}${this.funcsToScript(b.funcs)}\n\n` }, '') } @@ -184,13 +219,13 @@ export class IFQLPage extends PureComponent { } private handleAddNode = (name: string, bodyID: string): void => { - const script = this.state.body.reduce((acc, expression) => { - if (expression.id === bodyID) { - const {funcs} = expression + const script = this.state.body.reduce((acc, body) => { + if (body.id === bodyID) { + const {funcs} = body return `${acc}${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n` } - return acc + expression.source + return acc + body.source }, '') this.getASTResponse(script) diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index 289609efeb..2f5b8eb462 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -24,6 +24,7 @@ export interface DeleteFuncNodeArgs { export interface InputArg { funcID: string bodyID: string + declarationID?: string key: string value: string | boolean generate?: boolean diff --git a/ui/test/ifql/components/FuncArg.test.tsx b/ui/test/ifql/components/FuncArg.test.tsx index 65d5a5367e..7dabb0cc2a 100644 --- a/ui/test/ifql/components/FuncArg.test.tsx +++ b/ui/test/ifql/components/FuncArg.test.tsx @@ -7,6 +7,7 @@ const setup = () => { funcID: '', bodyID: '', funcName: '', + declarationID: '', argKey: '', value: '', type: '', From 8029b3860442423398db19d4b4283de1e5ac22b0 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Tue, 24 Apr 2018 13:41:39 -0700 Subject: [PATCH 07/34] Fix Deprecation to say PagerDuty v1 instead of v2 --- ui/src/kapacitor/components/AlertTabs.js | 2 +- ui/src/shared/apis/index.js | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/src/kapacitor/components/AlertTabs.js b/ui/src/kapacitor/components/AlertTabs.js index 2703378658..0f48d47aac 100644 --- a/ui/src/kapacitor/components/AlertTabs.js +++ b/ui/src/kapacitor/components/AlertTabs.js @@ -170,7 +170,7 @@ class AlertTabs extends Component { const showDeprecation = pagerDutyV1Enabled const pagerDutyDeprecationMessage = (
- PagerDuty v2 is being{' '} + PagerDuty v1 is being{' '} { { export const getAllServices = async kapacitor => { try { - const {data: {services}} = await kapacitorProxy( - kapacitor, - 'GET', - '/kapacitor/v1/service-tests' - ) + const { + data: {services}, + } = await kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests') return services } catch (error) { console.error(error) From 80495027edd87839d5cc5abe4bce4bd6a69cfa7c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 14:34:42 -0700 Subject: [PATCH 08/34] Repair adding a func node --- ui/src/ifql/components/ExpressionNode.tsx | 1 + ui/src/ifql/components/FuncSelector.tsx | 4 ++- ui/src/ifql/containers/IFQLPage.tsx | 25 +++++++++++++++---- ui/src/types/ifql.ts | 6 ++++- ui/test/ifql/components/FuncSelector.test.tsx | 5 ++-- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/ui/src/ifql/components/ExpressionNode.tsx b/ui/src/ifql/components/ExpressionNode.tsx index 2b57b2cfd4..c945acc3b7 100644 --- a/ui/src/ifql/components/ExpressionNode.tsx +++ b/ui/src/ifql/components/ExpressionNode.tsx @@ -27,6 +27,7 @@ class ExpressionNode extends PureComponent { bodyID={bodyID} funcs={funcNames} onAddNode={onAddNode} + declarationID={declarationID} />

{funcs.map(func => ( diff --git a/ui/src/ifql/components/FuncSelector.tsx b/ui/src/ifql/components/FuncSelector.tsx index a3e2c2a821..6e2fdae10e 100644 --- a/ui/src/ifql/components/FuncSelector.tsx +++ b/ui/src/ifql/components/FuncSelector.tsx @@ -15,6 +15,7 @@ interface State { interface Props { funcs: string[] bodyID: string + declarationID: string onAddNode: OnAddNode } @@ -65,8 +66,9 @@ export class FuncSelector extends PureComponent { } private handleAddNode = (name: string) => { + const {bodyID, declarationID} = this.props this.handleCloseList() - this.props.onAddNode(name, this.props.bodyID) + this.props.onAddNode(name, bodyID, declarationID) } private get availableFuncs() { diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index f4b95b3abb..cf0fcfd3cf 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -45,7 +45,7 @@ export class IFQLPage extends PureComponent { ast: null, suggestions: [], script: - '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', + 'foo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', } } @@ -218,19 +218,34 @@ export class IFQLPage extends PureComponent { this.setState({script}) } - private handleAddNode = (name: string, bodyID: string): void => { + private handleAddNode = ( + name: string, + bodyID: string, + declarationID: string + ): void => { const script = this.state.body.reduce((acc, body) => { if (body.id === bodyID) { - const {funcs} = body - return `${acc}${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n` + const declaration = body.declarations.find(d => d.id === declarationID) + if (declaration) { + return `${acc}${declaration.name} = ${this.appendFunc( + declaration.funcs, + name + )}` + } + + return `${acc}${this.appendFunc(body.funcs, name)}` } - return acc + body.source + return `${acc}${body.source}` }, '') this.getASTResponse(script) } + private appendFunc = (funcs, name): string => { + return `${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n` + } + private handleDeleteFuncNode = (ids: DeleteFuncNodeArgs): void => { const {funcID, declarationID = '', bodyID} = ids diff --git a/ui/src/types/ifql.ts b/ui/src/types/ifql.ts index 2f5b8eb462..65bd406f93 100644 --- a/ui/src/types/ifql.ts +++ b/ui/src/types/ifql.ts @@ -1,7 +1,11 @@ // function definitions export type OnDeleteFuncNode = (ids: DeleteFuncNodeArgs) => void export type OnChangeArg = (inputArg: InputArg) => void -export type OnAddNode = (bodyID: string, funcName: string) => void +export type OnAddNode = ( + bodyID: string, + funcName: string, + declarationID: string +) => void export type OnGenerateScript = (script: string) => void export type OnChangeScript = (script: string) => void export type OnSubmitScript = () => void diff --git a/ui/test/ifql/components/FuncSelector.test.tsx b/ui/test/ifql/components/FuncSelector.test.tsx index 1ec41d1273..e4dfa27b10 100644 --- a/ui/test/ifql/components/FuncSelector.test.tsx +++ b/ui/test/ifql/components/FuncSelector.test.tsx @@ -9,6 +9,7 @@ const setup = (override = {}) => { const props = { funcs: ['count', 'range'], bodyID: '1', + declarationID: '2', onAddNode: () => {}, ...override, } @@ -133,7 +134,7 @@ describe('IFQL.Components.FuncsButton', () => { const onAddNode = jest.fn() const {wrapper, props} = setup({onAddNode}) const [, func2] = props.funcs - const {bodyID} = props + const {bodyID, declarationID} = props const dropdownButton = wrapper.find('button') dropdownButton.simulate('click') @@ -148,7 +149,7 @@ describe('IFQL.Components.FuncsButton', () => { input.simulate('keyDown', {key: 'ArrowDown'}) input.simulate('keyDown', {key: 'Enter'}) - expect(onAddNode).toHaveBeenCalledWith(func2, bodyID) + expect(onAddNode).toHaveBeenCalledWith(func2, bodyID, declarationID) }) }) }) From 6d5924adb7673fcd6f18af2b68fe4e394d6bc491 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 14:49:44 -0700 Subject: [PATCH 09/34] Fix FROM db dropdown --- ui/src/ifql/components/From.tsx | 4 +++- ui/src/ifql/components/FuncArg.tsx | 1 + ui/src/ifql/containers/IFQLPage.tsx | 10 ++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ui/src/ifql/components/From.tsx b/ui/src/ifql/components/From.tsx index e8c8980e7a..4ecdf9b73b 100644 --- a/ui/src/ifql/components/From.tsx +++ b/ui/src/ifql/components/From.tsx @@ -10,6 +10,7 @@ interface Props { argKey: string value: string bodyID: string + declarationID: string onChangeArg: OnChangeArg } @@ -56,12 +57,13 @@ class From extends PureComponent { } private handleChooseDatabase = (item: DropdownItem): void => { - const {argKey, funcID, onChangeArg, bodyID} = this.props + const {argKey, funcID, onChangeArg, bodyID, declarationID} = this.props onChangeArg({ funcID, key: argKey, value: item.text, bodyID, + declarationID, generate: true, }) } diff --git a/ui/src/ifql/components/FuncArg.tsx b/ui/src/ifql/components/FuncArg.tsx index 389f199946..26427592ad 100644 --- a/ui/src/ifql/components/FuncArg.tsx +++ b/ui/src/ifql/components/FuncArg.tsx @@ -42,6 +42,7 @@ class FuncArg extends PureComponent { funcID={funcID} value={this.value} bodyID={bodyID} + declarationID={declarationID} onChangeArg={onChangeArg} /> ) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index cf0fcfd3cf..1ccf4289ca 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -182,8 +182,14 @@ export class IFQLPage extends PureComponent { private get bodyToScript(): string { return this.state.body.reduce((acc, b) => { if (b.declarations.length) { - const funcs = _.get(b, 'declarations.0.funcs', []) - return `${acc}${this.funcsToScript(funcs)}\n\n` + const declaration = _.get(b, 'declarations.0', false) + if (!declaration) { + return acc + } + + return `${acc}${declaration.name} = ${this.funcsToScript( + declaration.funcs + )}\n\n` } return `${acc}${this.funcsToScript(b.funcs)}\n\n` From 996c143536928f9c2fb1bdcaced2a068cf3b656e Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 14:51:38 -0700 Subject: [PATCH 10/34] Fix boolean arg --- ui/src/ifql/components/FuncArg.tsx | 1 + ui/src/ifql/components/FuncArgBool.tsx | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ui/src/ifql/components/FuncArg.tsx b/ui/src/ifql/components/FuncArg.tsx index 26427592ad..03ef847486 100644 --- a/ui/src/ifql/components/FuncArg.tsx +++ b/ui/src/ifql/components/FuncArg.tsx @@ -79,6 +79,7 @@ class FuncArg extends PureComponent { bodyID={bodyID} funcID={funcID} onChangeArg={onChangeArg} + declarationID={declarationID} onGenerateScript={onGenerateScript} /> ) diff --git a/ui/src/ifql/components/FuncArgBool.tsx b/ui/src/ifql/components/FuncArgBool.tsx index 0fe3fbd89b..e87b0f8dcd 100644 --- a/ui/src/ifql/components/FuncArgBool.tsx +++ b/ui/src/ifql/components/FuncArgBool.tsx @@ -8,6 +8,7 @@ interface Props { value: boolean funcID: string bodyID: string + declarationID: string onChangeArg: OnChangeArg onGenerateScript: () => void } @@ -23,8 +24,15 @@ class FuncArgBool extends PureComponent { } private handleToggle = (value: boolean): void => { - const {argKey, funcID, bodyID, onChangeArg} = this.props - onChangeArg({funcID, key: argKey, value, generate: true, bodyID}) + const {argKey, funcID, bodyID, onChangeArg, declarationID} = this.props + onChangeArg({ + key: argKey, + value, + funcID, + bodyID, + declarationID, + generate: true, + }) } } From eaa3ef4623be01c3422731f1c33c7170cddab66f Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Tue, 24 Apr 2018 14:52:12 -0700 Subject: [PATCH 11/34] Add custom error handling for line graphs --- .../data_explorer/containers/DataExplorer.tsx | 10 ---------- ui/src/shared/components/InvalidData.tsx | 17 +++++++++++++++++ ui/src/shared/components/LineGraph.js | 5 +++-- 3 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 ui/src/shared/components/InvalidData.tsx diff --git a/ui/src/data_explorer/containers/DataExplorer.tsx b/ui/src/data_explorer/containers/DataExplorer.tsx index 57ae430f96..9b6e59d026 100644 --- a/ui/src/data_explorer/containers/DataExplorer.tsx +++ b/ui/src/data_explorer/containers/DataExplorer.tsx @@ -1,5 +1,4 @@ import React, {PureComponent} from 'react' -import PropTypes from 'prop-types' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import {withRouter, InjectedRouter} from 'react-router' @@ -52,15 +51,6 @@ interface State { @ErrorHandling export class DataExplorer extends PureComponent { - public static childContextTypes = { - source: PropTypes.shape({ - links: PropTypes.shape({ - proxy: PropTypes.string.isRequired, - self: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, - } - constructor(props) { super(props) diff --git a/ui/src/shared/components/InvalidData.tsx b/ui/src/shared/components/InvalidData.tsx new file mode 100644 index 0000000000..52174b8136 --- /dev/null +++ b/ui/src/shared/components/InvalidData.tsx @@ -0,0 +1,17 @@ +import React, {PureComponent} from 'react' + +class InvalidData extends PureComponent<{}> { + public render() { + return ( +

+ The data returned from the query can't be visualized with this graph + type. Try updating the query or selecting a different graph type. +

+ ) + } +} + +export default InvalidData diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index f64411e6db..6d74de4313 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -6,9 +6,10 @@ import SingleStat from 'src/shared/components/SingleStat' import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers' import {colorsStringSchema} from 'shared/schemas' -import {ErrorHandling} from 'src/shared/decorators/errors' +import {ErrorHandlingWith} from 'src/shared/decorators/errors' +import InvalidData from 'src/shared/components/InvalidData' -@ErrorHandling +@ErrorHandlingWith(InvalidData, true) class LineGraph extends Component { constructor(props) { super(props) From f7457f7753f0ef6bc071f4cb2ae06ddc5a257e56 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Tue, 24 Apr 2018 15:00:08 -0700 Subject: [PATCH 12/34] Remove error handling debug flag --- ui/src/shared/components/LineGraph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 6d74de4313..d2cb421199 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -9,7 +9,7 @@ import {colorsStringSchema} from 'shared/schemas' import {ErrorHandlingWith} from 'src/shared/decorators/errors' import InvalidData from 'src/shared/components/InvalidData' -@ErrorHandlingWith(InvalidData, true) +@ErrorHandlingWith(InvalidData) class LineGraph extends Component { constructor(props) { super(props) From 2a2a315e4662c92fd2e8ff91f7a63c2f6b2a58df Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Tue, 24 Apr 2018 15:02:09 -0700 Subject: [PATCH 13/34] replace sortField and sortDirection everywhere --- ui/src/shared/components/TableGraph.js | 36 ++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/ui/src/shared/components/TableGraph.js b/ui/src/shared/components/TableGraph.js index 02e972c29a..b2c745a293 100644 --- a/ui/src/shared/components/TableGraph.js +++ b/ui/src/shared/components/TableGraph.js @@ -186,34 +186,29 @@ class TableGraph extends Component { } } - handleClickFieldName = fieldName => () => { + handleClickFieldName = clickedFieldName => () => { const {tableOptions} = this.props - const {timeFormat} = tableOptions - const {data, sortField, sortDirection} = this.state - const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true) + const {data, sort} = this.state const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT]) - let direction - if (fieldName === sortField) { - direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING + if (clickedFieldName === sort.field) { + sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING } else { - direction = DEFAULT_SORT_DIRECTION + sort.field = clickedFieldName + sort.direction = DEFAULT_SORT_DIRECTION } const {transformedData, sortedTimeVals} = transformTableData( data, - fieldName, - direction, - verticalTimeAxis, + sort, fieldNames, - timeFormat + tableOptions ) this.setState({ transformedData, sortedTimeVals, - sortField: fieldName, - sortDirection: direction, + sort, }) } @@ -251,8 +246,7 @@ class TableGraph extends Component { hoveredColumnIndex, hoveredRowIndex, transformedData, - sortField, - sortDirection, + sort, } = this.state const {tableOptions, colors} = parent.props @@ -318,9 +312,9 @@ class TableGraph extends Component { 'table-graph-cell__numerical': dataIsNumerical, 'table-graph-cell__field-name': isFieldName, 'table-graph-cell__sort-asc': - isFieldName && sortField === cellData && sortDirection === ASCENDING, + isFieldName && sort.field === cellData && sort.direction === ASCENDING, 'table-graph-cell__sort-desc': - isFieldName && sortField === cellData && sortDirection === DESCENDING, + isFieldName && sort.field === cellData && sort.direction === DESCENDING, }) const cellContents = isTimeData @@ -353,8 +347,7 @@ class TableGraph extends Component { hoveredColumnIndex, hoveredRowIndex, timeColumnWidth, - sortField, - sortDirection, + sort, transformedData, } = this.state const {hoverTime, tableOptions, colors} = this.props @@ -399,8 +392,7 @@ class TableGraph extends Component { enableFixedRowScroll={true} scrollToRow={scrollToRow} scrollToColumn={scrollToColumn} - sortField={sortField} - sortDirection={sortDirection} + sort={sort} cellRenderer={this.cellRenderer} hoveredColumnIndex={hoveredColumnIndex} hoveredRowIndex={hoveredRowIndex} From ca1cb97225e0f35c78e18efc614444739d50018f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 15:02:13 -0700 Subject: [PATCH 14/34] Fix not handling literal variable declarations --- ui/src/ifql/containers/IFQLPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index 1ccf4289ca..db8ba47c07 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -45,7 +45,7 @@ export class IFQLPage extends PureComponent { ast: null, suggestions: [], script: - 'foo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', + 'foo = "baz"\n\nfoo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', } } @@ -187,6 +187,10 @@ export class IFQLPage extends PureComponent { return acc } + if (!declaration.funcs) { + return `${acc}${b.source}\n\n` + } + return `${acc}${declaration.name} = ${this.funcsToScript( declaration.funcs )}\n\n` From f0ee615e548c85f44a41abf2e7208b1ba1eebc39 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 24 Apr 2018 15:02:19 -0700 Subject: [PATCH 15/34] Fix tests --- ui/test/ifql/components/From.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/test/ifql/components/From.test.tsx b/ui/test/ifql/components/From.test.tsx index 41466f527e..d859f9728c 100644 --- a/ui/test/ifql/components/From.test.tsx +++ b/ui/test/ifql/components/From.test.tsx @@ -10,6 +10,7 @@ const setup = () => { argKey: 'db', value: 'db1', bodyID: '2', + declarationID: '1', onChangeArg: () => {}, } From 3e5a27ce6f86dda28adb3b9903c096eb6dd6ebf7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 24 Apr 2018 16:49:38 -0700 Subject: [PATCH 16/34] Polish error state in line graph --- ui/src/shared/components/InvalidData.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ui/src/shared/components/InvalidData.tsx b/ui/src/shared/components/InvalidData.tsx index 52174b8136..17b50c5519 100644 --- a/ui/src/shared/components/InvalidData.tsx +++ b/ui/src/shared/components/InvalidData.tsx @@ -3,13 +3,12 @@ import React, {PureComponent} from 'react' class InvalidData extends PureComponent<{}> { public render() { return ( -

- The data returned from the query can't be visualized with this graph - type. Try updating the query or selecting a different graph type. -

+
+

+ The data returned from the query can't be visualized with this graph + type.
Try updating the query or selecting a different graph type. +

+
) } } From 0bdc032ce6db071beaa84d52dc77d42e7ac0ca5c Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 24 Apr 2018 17:14:11 -0700 Subject: [PATCH 17/34] Make auto refresh dropdown more compact --- .../shared/components/AutoRefreshDropdown.js | 4 ++-- ui/src/shared/data/autoRefreshes.js | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ui/src/shared/components/AutoRefreshDropdown.js b/ui/src/shared/components/AutoRefreshDropdown.js index 92f338cd8c..7a1d068307 100644 --- a/ui/src/shared/components/AutoRefreshDropdown.js +++ b/ui/src/shared/components/AutoRefreshDropdown.js @@ -41,7 +41,7 @@ class AutoRefreshDropdown extends Component { paused: +milliseconds === 0, })} > -
+
)}
  • - {preventCustomTimeRange ? '' : 'Relative '}Time Ranges + {preventCustomTimeRange ? '' : 'Relative '}Time
  • {timeRanges.map(item => { return ( diff --git a/ui/src/shared/data/timeRanges.js b/ui/src/shared/data/timeRanges.js index 914f0ddd73..1631bcd934 100644 --- a/ui/src/shared/data/timeRanges.js +++ b/ui/src/shared/data/timeRanges.js @@ -2,73 +2,73 @@ export const timeRanges = [ { defaultGroupBy: '10s', seconds: 300, - inputValue: 'Past 5 minutes', + inputValue: 'Past 5m', lower: 'now() - 5m', upper: null, - menuOption: 'Past 5 minutes', + menuOption: 'Past 5m', }, { defaultGroupBy: '1m', seconds: 900, - inputValue: 'Past 15 minutes', + inputValue: 'Past 15m', lower: 'now() - 15m', upper: null, - menuOption: 'Past 15 minutes', + menuOption: 'Past 15m', }, { defaultGroupBy: '1m', seconds: 3600, - inputValue: 'Past hour', + inputValue: 'Past 1h', lower: 'now() - 1h', upper: null, - menuOption: 'Past hour', + menuOption: 'Past 1h', }, { defaultGroupBy: '1m', seconds: 21600, - inputValue: 'Past 6 hours', + inputValue: 'Past 6h', lower: 'now() - 6h', upper: null, - menuOption: 'Past 6 hours', + menuOption: 'Past 6h', }, { defaultGroupBy: '5m', seconds: 43200, - inputValue: 'Past 12 hours', + inputValue: 'Past 12h', lower: 'now() - 12h', upper: null, - menuOption: 'Past 12 hours', + menuOption: 'Past 12h', }, { defaultGroupBy: '10m', seconds: 86400, - inputValue: 'Past 24 hours', + inputValue: 'Past 24h', lower: 'now() - 24h', upper: null, - menuOption: 'Past 24 hours', + menuOption: 'Past 24h', }, { defaultGroupBy: '30m', seconds: 172800, - inputValue: 'Past 2 days', + inputValue: 'Past 2d', lower: 'now() - 2d', upper: null, - menuOption: 'Past 2 days', + menuOption: 'Past 2d', }, { defaultGroupBy: '1h', seconds: 604800, - inputValue: 'Past 7 days', + inputValue: 'Past 7d', lower: 'now() - 7d', upper: null, - menuOption: 'Past 7 days', + menuOption: 'Past 7d', }, { defaultGroupBy: '6h', seconds: 2592000, - inputValue: 'Past 30 days', + inputValue: 'Past 30d', lower: 'now() - 30d', upper: null, - menuOption: 'Past 30 days', + menuOption: 'Past 30d', }, ] From 1589282891fd45ce95e95c193f9288997ba1eb5d Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 25 Apr 2018 12:29:58 -0700 Subject: [PATCH 19/34] Implement SubSections in Chronograf admin page --- .../chronograf/AdminChronografPage.js | 64 +++++++++++++++++-- ui/src/index.tsx | 5 +- ui/src/side_nav/containers/SideNav.tsx | 12 ++-- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/ui/src/admin/containers/chronograf/AdminChronografPage.js b/ui/src/admin/containers/chronograf/AdminChronografPage.js index 2815bd1fa4..5e1eb9f77d 100644 --- a/ui/src/admin/containers/chronograf/AdminChronografPage.js +++ b/ui/src/admin/containers/chronograf/AdminChronografPage.js @@ -2,10 +2,52 @@ import React from 'react' import PropTypes from 'prop-types' import {connect} from 'react-redux' -import AdminTabs from 'src/admin/components/chronograf/AdminTabs' +import SubSections from 'src/shared/components/SubSections' import FancyScrollbar from 'shared/components/FancyScrollbar' -const AdminChronografPage = ({me}) => ( +import UsersPage from 'src/admin/containers/chronograf/UsersPage' +import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage' +import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage' +import ProvidersPage from 'src/admin/containers/ProvidersPage' + +import { + isUserAuthorized, + ADMIN_ROLE, + SUPERADMIN_ROLE, +} from 'src/auth/Authorized' + +const sections = me => [ + { + url: 'current-organization', + name: 'Current Org', + enabled: isUserAuthorized(me.role, ADMIN_ROLE), + component: ( + + ), + }, + { + url: 'all-users', + name: 'All Users', + enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE), + component: , + }, + { + url: 'all-organizations', + name: 'All Orgs', + enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE), + component: ( + + ), + }, + { + url: 'organization-mappings', + name: 'Org Mappings', + enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE), + component: , + }, +] + +const AdminChronografPage = ({me, source, params: {tab}}) => (
    @@ -16,9 +58,12 @@ const AdminChronografPage = ({me}) => (
    -
    - -
    +
    @@ -35,6 +80,15 @@ AdminChronografPage.propTypes = { id: string.isRequired, }), }).isRequired, + params: shape({ + tab: string, + }).isRequired, + source: shape({ + id: string.isRequired, + links: shape({ + users: string.isRequired, + }), + }).isRequired, } const mapStateToProps = ({auth: {me}}) => ({ diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 5f8572b98f..9d8b0d651f 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -146,7 +146,10 @@ class Root extends PureComponent<{}, State> { path="kapacitors/:id/edit:hash" component={KapacitorPage} /> - + diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index 7c8e93e623..2240357b05 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -122,14 +122,16 @@ class SideNav extends PureComponent { - + Chronograf @@ -163,9 +165,7 @@ class SideNav extends PureComponent { const mapStateToProps = ({ auth: {isUsingAuth, logoutLink, me}, - app: { - ephemeral: {inPresentationMode}, - }, + app: {ephemeral: {inPresentationMode}}, links, }) => ({ isHidden: inPresentationMode, From 3afa98ce738a84b658b904026d988130a2af039e Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 25 Apr 2018 12:30:11 -0700 Subject: [PATCH 20/34] Remove obsolete component --- .../admin/components/chronograf/AdminTabs.js | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 ui/src/admin/components/chronograf/AdminTabs.js diff --git a/ui/src/admin/components/chronograf/AdminTabs.js b/ui/src/admin/components/chronograf/AdminTabs.js deleted file mode 100644 index 48ac81fd83..0000000000 --- a/ui/src/admin/components/chronograf/AdminTabs.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { - isUserAuthorized, - ADMIN_ROLE, - SUPERADMIN_ROLE, -} from 'src/auth/Authorized' - -import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs' -import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage' -import UsersPage from 'src/admin/containers/chronograf/UsersPage' -import ProvidersPage from 'src/admin/containers/ProvidersPage' -import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage' - -const ORGANIZATIONS_TAB_NAME = 'All Orgs' -const PROVIDERS_TAB_NAME = 'Org Mappings' -const CURRENT_ORG_USERS_TAB_NAME = 'Current Org' -const ALL_USERS_TAB_NAME = 'All Users' - -const AdminTabs = ({ - me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID}, -}) => { - const tabs = [ - { - requiredRole: ADMIN_ROLE, - type: CURRENT_ORG_USERS_TAB_NAME, - component: ( - - ), - }, - { - requiredRole: SUPERADMIN_ROLE, - type: ALL_USERS_TAB_NAME, - component: , - }, - { - requiredRole: SUPERADMIN_ROLE, - type: ORGANIZATIONS_TAB_NAME, - component: ( - - ), - }, - { - requiredRole: SUPERADMIN_ROLE, - type: PROVIDERS_TAB_NAME, - component: , - }, - ].filter(t => isUserAuthorized(meRole, t.requiredRole)) - - return ( - - - {tabs.map((t, i) => {tabs[i].type})} - - - {tabs.map((t, i) => ( - {t.component} - ))} - - - ) -} - -const {shape, string} = PropTypes - -AdminTabs.propTypes = { - me: shape({ - id: string.isRequired, - role: string.isRequired, - currentOrganization: shape({ - name: string.isRequired, - id: string.isRequired, - }), - }).isRequired, -} - -export default AdminTabs From 2892a22e04d4c7325e4d531b5cd6f93a71942166 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Wed, 25 Apr 2018 13:33:50 -0700 Subject: [PATCH 21/34] Handle responses from queries with errors --- ui/src/utils/groupBy.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ui/src/utils/groupBy.js b/ui/src/utils/groupBy.js index 74e9e7b09a..dc6869c7aa 100644 --- a/ui/src/utils/groupBy.js +++ b/ui/src/utils/groupBy.js @@ -2,8 +2,11 @@ import _ from 'lodash' import {shiftDate} from 'shared/query/helpers' import {map, reduce, forEach, concat, clone} from 'fast.js' -const groupByMap = (responses, responseIndex, groupByColumns) => { - const firstColumns = _.get(responses, [0, 'series', 0, 'columns']) +const groupByMap = (results, responseIndex, groupByColumns) => { + if (_.isEmpty(results)) { + return [] + } + const firstColumns = _.get(results, [0, 'series', 0, 'columns']) const accum = [ { responseIndex, @@ -15,14 +18,14 @@ const groupByMap = (responses, responseIndex, groupByColumns) => { ...firstColumns.slice(1), ], groupByColumns, - name: _.get(responses, [0, 'series', 0, 'name']), + name: _.get(results, [0, 'series', 0, 'name']), values: [], }, ], }, ] - const seriesArray = _.get(responses, [0, 'series']) + const seriesArray = _.get(results, [0, 'series']) seriesArray.forEach(s => { const prevValues = accum[0].series[0].values const tagsToAdd = groupByColumns.map(gb => s.tags[gb]) @@ -35,13 +38,17 @@ const groupByMap = (responses, responseIndex, groupByColumns) => { const constructResults = (raw, groupBys) => { return _.flatten( map(raw, (response, index) => { - const responses = _.get(response, 'response.results', []) + const results = _.get(response, 'response.results', []) + + const successfulResults = _.filter( + results, + r => !_.get(r, 'error', false) + ) if (groupBys[index]) { - return groupByMap(responses, index, groupBys[index]) + return groupByMap(successfulResults, index, groupBys[index]) } - - return map(responses, r => ({...r, responseIndex: index})) + return map(successfulResults, r => ({...r, responseIndex: index})) }) ) } @@ -221,8 +228,8 @@ export const groupByTimeSeriesTransform = (raw, groupBys) => { if (!groupBys) { groupBys = Array(raw.length).fill(false) } - const results = constructResults(raw, groupBys) + const results = constructResults(raw, groupBys) const serieses = constructSerieses(results) const {cells, sortedLabels, seriesLabels} = constructCells(serieses) From 3615f13c21c7c14030d25412f05bbb9035b5be6b Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Wed, 25 Apr 2018 14:26:00 -0700 Subject: [PATCH 22/34] Pass ability to editQueryStatus to all graph types --- ui/src/shared/components/RefreshingGraph.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 12c935ae1a..34ad1fbabd 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -63,6 +63,7 @@ const RefreshingGraph = ({ templates={templates} autoRefresh={autoRefresh} cellHeight={cellHeight} + editQueryStatus={editQueryStatus} prefix={prefix} suffix={suffix} inView={inView} @@ -81,6 +82,7 @@ const RefreshingGraph = ({ autoRefresh={autoRefresh} cellHeight={cellHeight} resizerTopHeight={resizerTopHeight} + editQueryStatus={editQueryStatus} resizeCoords={resizeCoords} cellID={cellID} prefix={prefix} @@ -105,6 +107,7 @@ const RefreshingGraph = ({ cellHeight={cellHeight} resizeCoords={resizeCoords} tableOptions={tableOptions} + editQueryStatus={editQueryStatus} resizerTopHeight={resizerTopHeight} handleSetHoverTime={handleSetHoverTime} isInCEO={isInCEO} From 973d514c04b4dca77d75b7676c79db07c48512be Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Wed, 25 Apr 2018 14:35:36 -0700 Subject: [PATCH 23/34] Properly handle strings in data for line graphs --- ui/src/shared/components/LineGraph.js | 31 +++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index d2cb421199..9338ba7d96 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -1,3 +1,4 @@ +import _ from 'lodash' import React, {Component} from 'react' import PropTypes from 'prop-types' import Dygraph from 'shared/components/Dygraph' @@ -9,15 +10,36 @@ import {colorsStringSchema} from 'shared/schemas' import {ErrorHandlingWith} from 'src/shared/decorators/errors' import InvalidData from 'src/shared/components/InvalidData' +const validateTimeSeries = timeseries => { + return _.every(timeseries, r => + _.every(r, (v, i) => { + if (i === 0) { + return true + } + return _.isNumber(v) || _.isNull(v) + }) + ) +} @ErrorHandlingWith(InvalidData) class LineGraph extends Component { constructor(props) { super(props) + this.invalidData = false } componentWillMount() { const {data, isInDataExplorer} = this.props + this.parseTimeSeries(data, isInDataExplorer) + } + + parseTimeSeries(data, isInDataExplorer) { this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer) + const valid = validateTimeSeries(_.get(this._timeSeries, 'timeSeries', [])) + if (valid) { + this.invalidData = false + } else { + this.invalidData = true + } } componentWillUpdate(nextProps) { @@ -26,14 +48,15 @@ class LineGraph extends Component { data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex ) { - this._timeSeries = timeSeriesToDygraph( - nextProps.data, - nextProps.isInDataExplorer - ) + this.parseTimeSeries(nextProps.data, nextProps.isInDataExplorer) } } render() { + if (this.invalidData) { + return + } + const { data, axes, From d9455a7646e56c2f445a0123762505bb6cfcd600 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Wed, 25 Apr 2018 14:53:54 -0700 Subject: [PATCH 24/34] Restore groupBy naming behavior for lineGraphs --- ui/src/utils/groupBy.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/utils/groupBy.js b/ui/src/utils/groupBy.js index dc6869c7aa..cf23a6cd81 100644 --- a/ui/src/utils/groupBy.js +++ b/ui/src/utils/groupBy.js @@ -99,11 +99,15 @@ const constructCells = serieses => { vals, })) + const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`) + .sort() + .join('') + const unsortedLabels = map(columns.slice(1), (field, i) => ({ label: groupByColumns && i <= groupByColumns.length - 1 ? `${field}` - : `${measurement}.${field}`, + : `${measurement}.${field}${tagSet}`, responseIndex, seriesIndex, })) From f1d197a4322f6dc36020dda41a2f33a28c6263e3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 25 Apr 2018 15:10:15 -0700 Subject: [PATCH 25/34] Prettier --- ui/src/side_nav/containers/SideNav.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index 2240357b05..5f28254bf7 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -165,7 +165,9 @@ class SideNav extends PureComponent { const mapStateToProps = ({ auth: {isUsingAuth, logoutLink, me}, - app: {ephemeral: {inPresentationMode}}, + app: { + ephemeral: {inPresentationMode}, + }, links, }) => ({ isHidden: inPresentationMode, From e0dc57e4e35c2e5a6d313641f535340413e31f6a Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Wed, 25 Apr 2018 15:11:01 -0700 Subject: [PATCH 26/34] Fix linter error. --- ui/src/side_nav/containers/SideNav.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index 2240357b05..5f28254bf7 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -165,7 +165,9 @@ class SideNav extends PureComponent { const mapStateToProps = ({ auth: {isUsingAuth, logoutLink, me}, - app: {ephemeral: {inPresentationMode}}, + app: { + ephemeral: {inPresentationMode}, + }, links, }) => ({ isHidden: inPresentationMode, From 8b52c83c408f2da2f40b4ad1eb870944fec06668 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Wed, 25 Apr 2018 16:33:47 -0700 Subject: [PATCH 27/34] Stricter check for line graph data --- ui/src/shared/components/LineGraph.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js index 9338ba7d96..47b5145b34 100644 --- a/ui/src/shared/components/LineGraph.js +++ b/ui/src/shared/components/LineGraph.js @@ -12,19 +12,17 @@ import InvalidData from 'src/shared/components/InvalidData' const validateTimeSeries = timeseries => { return _.every(timeseries, r => - _.every(r, (v, i) => { - if (i === 0) { - return true - } - return _.isNumber(v) || _.isNull(v) - }) + _.every( + r, + (v, i) => (i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v) + ) ) } @ErrorHandlingWith(InvalidData) class LineGraph extends Component { constructor(props) { super(props) - this.invalidData = false + this.isValidData = true } componentWillMount() { @@ -34,12 +32,9 @@ class LineGraph extends Component { parseTimeSeries(data, isInDataExplorer) { this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer) - const valid = validateTimeSeries(_.get(this._timeSeries, 'timeSeries', [])) - if (valid) { - this.invalidData = false - } else { - this.invalidData = true - } + this.isValidData = validateTimeSeries( + _.get(this._timeSeries, 'timeSeries', []) + ) } componentWillUpdate(nextProps) { @@ -53,7 +48,7 @@ class LineGraph extends Component { } render() { - if (this.invalidData) { + if (!this.isValidData) { return } From 78bfe2cec33f4908c28bace1c9063a8815848a75 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Wed, 25 Apr 2018 17:05:56 -0700 Subject: [PATCH 28/34] PR review suggestions --- ui/src/utils/groupBy.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/ui/src/utils/groupBy.js b/ui/src/utils/groupBy.js index cf23a6cd81..ff6cd898e5 100644 --- a/ui/src/utils/groupBy.js +++ b/ui/src/utils/groupBy.js @@ -18,7 +18,7 @@ const groupByMap = (results, responseIndex, groupByColumns) => { ...firstColumns.slice(1), ], groupByColumns, - name: _.get(results, [0, 'series', 0, 'name']), + name: _.get(results, [0, 'series', 0, 'name'], ''), values: [], }, ], @@ -40,10 +40,7 @@ const constructResults = (raw, groupBys) => { map(raw, (response, index) => { const results = _.get(response, 'response.results', []) - const successfulResults = _.filter( - results, - r => !_.get(r, 'error', false) - ) + const successfulResults = _.filter(results, r => _.isNil(r.error)) if (groupBys[index]) { return groupByMap(successfulResults, index, groupBys[index]) @@ -88,16 +85,14 @@ const constructCells = serieses => { name: measurement, columns, groupByColumns, - values, + values = [], seriesIndex, responseIndex, tags = {}, }, ind ) => { - const rows = map(values || [], vals => ({ - vals, - })) + const rows = map(values, vals => ({vals})) const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`) .sort() From 2656f610d5793704a142d8a50e30b21701dd616a Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Thu, 26 Apr 2018 16:23:38 -0700 Subject: [PATCH 29/34] Return correct value in column for falsey data Co-authored-by: Brandon Farmer Co-authored-by: Deniz Kusefoglu --- ui/src/dashboards/utils/tableGraph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/dashboards/utils/tableGraph.ts b/ui/src/dashboards/utils/tableGraph.ts index a31e3af9dd..881d12c2b6 100644 --- a/ui/src/dashboards/utils/tableGraph.ts +++ b/ui/src/dashboards/utils/tableGraph.ts @@ -147,7 +147,7 @@ export const orderTableColumns = (data, fieldNames) => { }) const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1) const orderedData = map(data, row => { - return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]] || v) + return row.map((__, j, arr) => arr[filteredFieldSortOrder[j]]) }) return orderedData[0].length ? orderedData : [[]] } From bfdce4594fe56b477dac53d1d80a3d88ffbb8928 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 25 Apr 2018 10:45:56 -0700 Subject: [PATCH 30/34] Fix formatting for body sources missing newlines --- ui/src/ifql/components/BodyBuilder.tsx | 23 ++++++++++------ ui/src/ifql/components/ExpressionNode.tsx | 14 +++++----- ui/src/ifql/containers/IFQLPage.tsx | 32 +++++++++++++++++------ ui/src/style/components/func-node.scss | 1 - 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/ui/src/ifql/components/BodyBuilder.tsx b/ui/src/ifql/components/BodyBuilder.tsx index a6d8d6f647..e81855fa64 100644 --- a/ui/src/ifql/components/BodyBuilder.tsx +++ b/ui/src/ifql/components/BodyBuilder.tsx @@ -21,17 +21,24 @@ class BodyBuilder extends PureComponent { return b.declarations.map(d => { if (d.funcs) { return ( - +
    +
    {d.name} =
    + +
    ) } - return
    {b.source}
    + return ( +
    + {b.source} +
    + ) }) } diff --git a/ui/src/ifql/components/ExpressionNode.tsx b/ui/src/ifql/components/ExpressionNode.tsx index c945acc3b7..8ef81749bb 100644 --- a/ui/src/ifql/components/ExpressionNode.tsx +++ b/ui/src/ifql/components/ExpressionNode.tsx @@ -22,14 +22,6 @@ class ExpressionNode extends PureComponent { {({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => { return (
    -

    - -

    {funcs.map(func => ( { onGenerateScript={onGenerateScript} /> ))} +
    ) }} diff --git a/ui/src/ifql/containers/IFQLPage.tsx b/ui/src/ifql/containers/IFQLPage.tsx index db8ba47c07..2ad7d99b42 100644 --- a/ui/src/ifql/containers/IFQLPage.tsx +++ b/ui/src/ifql/containers/IFQLPage.tsx @@ -45,7 +45,7 @@ export class IFQLPage extends PureComponent { ast: null, suggestions: [], script: - 'foo = "baz"\n\nfoo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', + 'baz = "baz"\n\nfoo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n', } } @@ -234,7 +234,9 @@ export class IFQLPage extends PureComponent { declarationID: string ): void => { const script = this.state.body.reduce((acc, body) => { - if (body.id === bodyID) { + const {id, source, funcs} = body + + if (id === bodyID) { const declaration = body.declarations.find(d => d.id === declarationID) if (declaration) { return `${acc}${declaration.name} = ${this.appendFunc( @@ -243,10 +245,10 @@ export class IFQLPage extends PureComponent { )}` } - return `${acc}${this.appendFunc(body.funcs, name)}` + return `${acc}${this.appendFunc(funcs, name)}` } - return `${acc}${body.source}` + return `${acc}${this.formatSource(source)}` }, '') this.getASTResponse(script) @@ -262,7 +264,7 @@ export class IFQLPage extends PureComponent { const script = this.state.body .map((body, bodyIndex) => { if (body.id !== bodyID) { - return body.source + return this.formatSource(body.source) } const isLast = bodyIndex === this.state.body.length - 1 @@ -278,24 +280,38 @@ export class IFQLPage extends PureComponent { const functions = declaration.funcs.filter(f => f.id !== funcID) const s = this.funcsToSource(functions) - return `${declaration.name} = ${this.parseLastSource(s, isLast)}` + return `${declaration.name} = ${this.formatLastSource(s, isLast)}` } const funcs = body.funcs.filter(f => f.id !== funcID) const source = this.funcsToSource(funcs) - return this.parseLastSource(source, isLast) + return this.formatLastSource(source, isLast) }) .join('') this.getASTResponse(script) } + private formatSource = (source: string): string => { + // currently a bug in the AST which does not add newlines to literal variable assignment bodies + if (!source.match(/\n\n/)) { + return `${source}\n\n` + } + + return `${source}` + } + // formats the last line of a body string to include two new lines - private parseLastSource = (source: string, isLast: boolean): string => { + private formatLastSource = (source: string, isLast: boolean): string => { if (isLast) { return `${source}` } + // currently a bug in the AST which does not add newlines to literal variable assignment bodies + if (!source.match(/\n\n/)) { + return `${source}\n\n` + } + return `${source}\n\n` } diff --git a/ui/src/style/components/func-node.scss b/ui/src/style/components/func-node.scss index 84c3b13742..82dcfed978 100644 --- a/ui/src/style/components/func-node.scss +++ b/ui/src/style/components/func-node.scss @@ -14,7 +14,6 @@ width: auto; display: flex; color: $ix-text-default; - text-transform: uppercase; margin-bottom: $ix-marg-a; font-family: $ix-text-font; font-weight: 500; From 844d860cd9ae9d2b6201219c387b130f110b456c Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Fri, 27 Apr 2018 13:52:07 -0700 Subject: [PATCH 31/34] Convert and refactor AutoRefresh --- ui/src/shared/apis/query.ts | 74 ++++++ ui/src/shared/components/AutoRefresh.js | 294 ----------------------- ui/src/shared/components/AutoRefresh.tsx | 294 +++++++++++++++++++++++ ui/src/shared/components/Crosshair.tsx | 3 +- ui/src/shared/constants/series.ts | 7 + 5 files changed, 377 insertions(+), 295 deletions(-) create mode 100644 ui/src/shared/apis/query.ts delete mode 100644 ui/src/shared/components/AutoRefresh.js create mode 100644 ui/src/shared/components/AutoRefresh.tsx create mode 100644 ui/src/shared/constants/series.ts diff --git a/ui/src/shared/apis/query.ts b/ui/src/shared/apis/query.ts new file mode 100644 index 0000000000..03eab77837 --- /dev/null +++ b/ui/src/shared/apis/query.ts @@ -0,0 +1,74 @@ +import _ from 'lodash' +import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries' +import {removeUnselectedTemplateValues} from 'src/dashboards/constants' + +import {intervalValuesPoints} from 'src/shared/constants' + +interface TemplateQuery { + db: string + rp: string + influxql: string +} + +interface TemplateValue { + type: string + value: string + selected: boolean +} + +interface Template { + type: string + tempVar: string + query: TemplateQuery + values: TemplateValue[] +} + +interface Query { + host: string | string[] + text: string + database: string + db: string + rp: string +} + +export const fetchTimeSeries = async ( + queries: Query[], + resolution: number, + templates: Template[], + editQueryStatus: () => void +) => { + const timeSeriesPromises = queries.map(query => { + const {host, database, rp} = query + // the key `database` was used upstream in HostPage.js, and since as of this writing + // the codebase has not been fully converted to TypeScript, it's not clear where else + // it may be used, but this slight modification is intended to allow for the use of + // `database` while moving over to `db` for consistency over time + const db = _.get(query, 'db', database) + + const templatesWithIntervalVals = templates.map(temp => { + if (temp.tempVar === ':interval:') { + if (resolution) { + const values = temp.values.map(v => ({ + ...v, + value: `${_.toInteger(Number(resolution) / 3)}`, + })) + + return {...temp, values} + } + + return {...temp, values: intervalValuesPoints} + } + return temp + }) + + const tempVars = removeUnselectedTemplateValues(templatesWithIntervalVals) + + const source = host + return fetchTimeSeriesAsync( + {source, db, rp, query, tempVars, resolution}, + editQueryStatus + ) + }) + + return Promise.all(timeSeriesPromises) +} diff --git a/ui/src/shared/components/AutoRefresh.js b/ui/src/shared/components/AutoRefresh.js deleted file mode 100644 index dda8bc5d00..0000000000 --- a/ui/src/shared/components/AutoRefresh.js +++ /dev/null @@ -1,294 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import _ from 'lodash' - -import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries' -import {removeUnselectedTemplateValues} from 'src/dashboards/constants' -import {intervalValuesPoints} from 'src/shared/constants' -import {getQueryConfig} from 'shared/apis' - -const AutoRefresh = ComposedComponent => { - class wrapper extends Component { - constructor() { - super() - this.state = { - lastQuerySuccessful: true, - timeSeries: [], - resolution: null, - queryASTs: [], - } - } - - async componentDidMount() { - const {queries, templates, autoRefresh, type} = this.props - this.executeQueries(queries, templates) - if (type === 'table') { - const queryASTs = await this.getQueryASTs(queries, templates) - this.setState({queryASTs}) - } - if (autoRefresh) { - this.intervalID = setInterval( - () => this.executeQueries(queries, templates), - autoRefresh - ) - } - } - - getQueryASTs = async (queries, templates) => { - return await Promise.all( - queries.map(async q => { - const host = _.isArray(q.host) ? q.host[0] : q.host - const url = host.replace('proxy', 'queries') - const text = q.text - const {data} = await getQueryConfig(url, [{query: text}], templates) - return data.queries[0].queryAST - }) - ) - } - - async componentWillReceiveProps(nextProps) { - const inViewDidUpdate = this.props.inView !== nextProps.inView - - const queriesDidUpdate = this.queryDifference( - this.props.queries, - nextProps.queries - ).length - - const tempVarsDidUpdate = !_.isEqual( - this.props.templates, - nextProps.templates - ) - - const shouldRefetch = - queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate - - if (shouldRefetch) { - if (this.props.type === 'table') { - const queryASTs = await this.getQueryASTs( - nextProps.queries, - nextProps.templates - ) - this.setState({queryASTs}) - } - - this.executeQueries( - nextProps.queries, - nextProps.templates, - nextProps.inView - ) - } - - if (this.props.autoRefresh !== nextProps.autoRefresh || shouldRefetch) { - clearInterval(this.intervalID) - - if (nextProps.autoRefresh) { - this.intervalID = setInterval( - () => - this.executeQueries( - nextProps.queries, - nextProps.templates, - nextProps.inView - ), - nextProps.autoRefresh - ) - } - } - } - - queryDifference = (left, right) => { - const leftStrs = left.map(q => `${q.host}${q.text}`) - const rightStrs = right.map(q => `${q.host}${q.text}`) - return _.difference( - _.union(leftStrs, rightStrs), - _.intersection(leftStrs, rightStrs) - ) - } - - executeQueries = async ( - queries, - templates = [], - inView = this.props.inView - ) => { - const {editQueryStatus, grabDataForDownload} = this.props - const {resolution} = this.state - if (!inView) { - return - } - if (!queries.length) { - this.setState({timeSeries: []}) - return - } - - this.setState({isFetching: true}) - - const timeSeriesPromises = queries.map(query => { - const {host, database, rp} = query - // the key `database` was used upstream in HostPage.js, and since as of this writing - // the codebase has not been fully converted to TypeScript, it's not clear where else - // it may be used, but this slight modification is intended to allow for the use of - // `database` while moving over to `db` for consistency over time - const db = _.get(query, 'db', database) - - const templatesWithIntervalVals = templates.map(temp => { - if (temp.tempVar === ':interval:') { - if (resolution) { - // resize event - return { - ...temp, - values: temp.values.map(v => ({ - ...v, - value: `${_.toInteger(Number(resolution) / 3)}`, - })), - } - } - - return { - ...temp, - values: intervalValuesPoints, - } - } - return temp - }) - - const tempVars = removeUnselectedTemplateValues( - templatesWithIntervalVals - ) - return fetchTimeSeriesAsync( - { - source: host, - db, - rp, - query, - tempVars, - resolution, - }, - editQueryStatus - ) - }) - - try { - const timeSeries = await Promise.all(timeSeriesPromises) - const newSeries = timeSeries.map(response => ({response})) - const lastQuerySuccessful = this._resultsForQuery(newSeries) - - this.setState({ - timeSeries: newSeries, - lastQuerySuccessful, - isFetching: false, - }) - - if (grabDataForDownload) { - grabDataForDownload(timeSeries) - } - } catch (err) { - console.error(err) - } - } - - componentWillUnmount() { - clearInterval(this.intervalID) - this.intervalID = false - } - - setResolution = resolution => { - if (resolution !== this.state.resolution) { - this.setState({resolution}) - } - } - - render() { - const {timeSeries, queryASTs} = this.state - if (this.state.isFetching && this.state.lastQuerySuccessful) { - return ( - - ) - } - - return ( - - ) - } - - _resultsForQuery = data => - data.length - ? data.every(({response}) => - _.get(response, 'results', []).every( - result => - Object.keys(result).filter(k => k !== 'statement_id').length !== - 0 - ) - ) - : false - } - - wrapper.defaultProps = { - inView: true, - } - - const { - array, - arrayOf, - bool, - element, - func, - number, - oneOfType, - shape, - string, - } = PropTypes - - wrapper.propTypes = { - type: string.isRequired, - children: element, - autoRefresh: number.isRequired, - inView: bool, - templates: arrayOf( - shape({ - type: string.isRequired, - tempVar: string.isRequired, - query: shape({ - db: string, - rp: string, - influxql: string, - }), - values: arrayOf( - shape({ - type: string.isRequired, - value: string.isRequired, - selected: bool, - }) - ).isRequired, - }) - ), - queries: arrayOf( - shape({ - host: oneOfType([string, arrayOf(string)]), - text: string, - }).isRequired - ).isRequired, - axes: shape({ - bounds: shape({ - y: array, - y2: array, - }), - }), - editQueryStatus: func, - grabDataForDownload: func, - } - - return wrapper -} - -export default AutoRefresh diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx new file mode 100644 index 0000000000..833cb00d9a --- /dev/null +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -0,0 +1,294 @@ +import React, {Component, ComponentClass} from 'react' +import _ from 'lodash' + +import {getQueryConfig} from 'src/shared/apis' +import {fetchTimeSeries} from 'src/shared/apis/query' +import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series' + +interface Axes { + bounds: { + y: number[] + y2: number[] + } +} + +interface Query { + host: string | string[] + text: string + database: string + db: string + rp: string +} + +interface TemplateQuery { + db: string + rp: string + influxql: string +} + +interface TemplateValue { + type: string + value: string + selected: boolean +} + +interface Template { + type: string + tempVar: string + query: TemplateQuery + values: TemplateValue[] +} + +interface Props { + type: string + autoRefresh: number + inView: boolean + templates: Template[] + queries: Query[] + axes: Axes + editQueryStatus: () => void + grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void +} + +type TimeSeriesValue = string | number | Date | null + +interface Series { + name: string + columns: string[] + values: TimeSeriesValue[] +} + +interface Result { + series: Series[] + statement_id: number +} + +interface TimeSeriesResponse { + results: Result[] +} + +interface TimeSeriesServerResponse { + response: TimeSeriesResponse +} + +interface QueryAST { + groupBy?: { + tags: string[] + } +} + +interface State { + isFetching: boolean + isLastQuerySuccessful: boolean + timeSeries: TimeSeriesServerResponse[] + resolution: number | null + queryASTs?: QueryAST[] +} + +interface OriginalProps { + data: TimeSeriesServerResponse[] + setResolution: (resolution: number) => void + isFetchingInitially?: boolean + isRefreshing?: boolean + queryASTs?: any[] +} + +const AutoRefresh = ( + ComposedComponent: ComponentClass +) => { + class Wrapper extends Component { + public static defaultProps = { + inView: true, + } + + private intervalID: NodeJS.Timer | null + + constructor(props: Props) { + super(props) + this.state = { + isFetching: false, + isLastQuerySuccessful: true, + timeSeries: DEFAULT_TIME_SERIES, + resolution: null, + queryASTs: [], + } + } + + public async componentDidMount() { + if (this.isTable) { + const queryASTs = await this.getQueryASTs() + this.setState({queryASTs}) + } + + this.startNewPolling() + } + + public async componentDidUpdate(prevProps: Props) { + if (!this.isPropsDifferent(prevProps)) { + return + } + + if (this.isTable) { + const queryASTs = await this.getQueryASTs() + this.setState({queryASTs}) + } + + this.startNewPolling() + } + + public executeQueries = async () => { + const {editQueryStatus, grabDataForDownload, inView, queries} = this.props + const {resolution} = this.state + + if (!inView) { + return + } + + if (!queries.length) { + this.setState({timeSeries: DEFAULT_TIME_SERIES}) + return + } + + this.setState({isFetching: true}) + const templates: Template[] = _.get(this.props, 'templates', []) + + try { + const timeSeries = await fetchTimeSeries( + queries, + resolution, + templates, + editQueryStatus + ) + const newSeries = timeSeries.map((response: TimeSeriesResponse) => ({ + response, + })) + const isLastQuerySuccessful = this.resultsForQuery(newSeries) + + this.setState({ + timeSeries: newSeries, + isLastQuerySuccessful, + isFetching: false, + }) + + if (grabDataForDownload) { + grabDataForDownload(newSeries) + } + } catch (err) { + console.error(err) + } + } + + public componentWillUnmount() { + this.clearInterval() + } + + public render() { + const { + timeSeries, + queryASTs, + isFetching, + isLastQuerySuccessful, + } = this.state + + if (isFetching && isLastQuerySuccessful) { + return ( + + ) + } + + return ( + + ) + } + + private setResolution = resolution => { + if (resolution !== this.state.resolution) { + this.setState({resolution}) + } + } + + private clearInterval() { + if (!this.intervalID) { + return + } + + clearInterval(this.intervalID) + this.intervalID = null + } + + private isPropsDifferent(nextProps: Props) { + return ( + this.props.inView !== nextProps.inView || + !!this.queryDifference(this.props.queries, nextProps.queries).length || + !_.isEqual(this.props.templates, nextProps.templates) || + this.props.autoRefresh !== nextProps.autoRefresh + ) + } + + private startNewPolling() { + this.clearInterval() + + const {autoRefresh} = this.props + + this.executeQueries() + + if (autoRefresh) { + this.intervalID = setInterval(this.executeQueries, autoRefresh) + } + } + + private queryDifference = (left, right) => { + const mapper = q => `${q.host}${q.text}` + const leftStrs = left.map(mapper) + const rightStrs = right.map(mapper) + return _.difference( + _.union(leftStrs, rightStrs), + _.intersection(leftStrs, rightStrs) + ) + } + + private get isTable(): boolean { + return this.props.type === 'table' + } + + private getQueryASTs = async (): Promise => { + const {queries, templates} = this.props + + return await Promise.all( + queries.map(async q => { + const host = _.isArray(q.host) ? q.host[0] : q.host + const url = host.replace('proxy', 'queries') + const text = q.text + const {data} = await getQueryConfig(url, [{query: text}], templates) + return data.queries[0].queryAST + }) + ) + } + + private resultsForQuery = data => { + if (!data.length) { + return false + } + + data.every(({resp}) => + _.get(resp, 'results', []).every(r => Object.keys(r).length > 1) + ) + } + } + + return Wrapper +} + +export default AutoRefresh diff --git a/ui/src/shared/components/Crosshair.tsx b/ui/src/shared/components/Crosshair.tsx index fbd972a262..79b63af286 100644 --- a/ui/src/shared/components/Crosshair.tsx +++ b/ui/src/shared/components/Crosshair.tsx @@ -1,3 +1,4 @@ +import _ from 'lodash' import React, {PureComponent} from 'react' import Dygraph from 'dygraphs' import {connect} from 'react-redux' @@ -35,7 +36,7 @@ class Crosshair extends PureComponent { private get isVisible() { const {hoverTime} = this.props - return hoverTime !== 0 + return hoverTime !== 0 && _.isFinite(hoverTime) } private get crosshairLeft(): number { diff --git a/ui/src/shared/constants/series.ts b/ui/src/shared/constants/series.ts new file mode 100644 index 0000000000..3177b6d3e3 --- /dev/null +++ b/ui/src/shared/constants/series.ts @@ -0,0 +1,7 @@ +export const DEFAULT_TIME_SERIES = [ + { + response: { + results: [], + }, + }, +] From bb5e6232732925c7eee8a654adc747abf2584162 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Fri, 27 Apr 2018 17:05:13 -0700 Subject: [PATCH 32/34] Show message when there are no results --- ui/src/shared/components/AutoRefresh.tsx | 41 +++++++++++------------- ui/src/types/series.ts | 20 ++++++++++++ 2 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 ui/src/types/series.ts diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx index 833cb00d9a..ce5a37b7f5 100644 --- a/ui/src/shared/components/AutoRefresh.tsx +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -4,6 +4,7 @@ import _ from 'lodash' import {getQueryConfig} from 'src/shared/apis' import {fetchTimeSeries} from 'src/shared/apis/query' import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series' +import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series' interface Axes { bounds: { @@ -50,27 +51,6 @@ interface Props { grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void } -type TimeSeriesValue = string | number | Date | null - -interface Series { - name: string - columns: string[] - values: TimeSeriesValue[] -} - -interface Result { - series: Series[] - statement_id: number -} - -interface TimeSeriesResponse { - results: Result[] -} - -interface TimeSeriesServerResponse { - response: TimeSeriesResponse -} - interface QueryAST { groupBy?: { tags: string[] @@ -162,7 +142,7 @@ const AutoRefresh = ( const newSeries = timeSeries.map((response: TimeSeriesResponse) => ({ response, })) - const isLastQuerySuccessful = this.resultsForQuery(newSeries) + const isLastQuerySuccessful = this.hasResultsForQuery(newSeries) this.setState({ timeSeries: newSeries, @@ -190,6 +170,21 @@ const AutoRefresh = ( isLastQuerySuccessful, } = this.state + const hasValues = _.some(timeSeries, s => { + const results = _.get(s, 'response.results', []) + const v = _.some(results, r => r.series) + console.error(results, v) + return v + }) + + if (!hasValues) { + return ( +
    +

    No Results

    +
    + ) + } + if (isFetching && isLastQuerySuccessful) { return ( { + private hasResultsForQuery = (data): boolean => { if (!data.length) { return false } diff --git a/ui/src/types/series.ts b/ui/src/types/series.ts new file mode 100644 index 0000000000..3293f9a95e --- /dev/null +++ b/ui/src/types/series.ts @@ -0,0 +1,20 @@ +export type TimeSeriesValue = string | number | Date | null + +export interface Series { + name: string + columns: string[] + values: TimeSeriesValue[] +} + +export interface Result { + series: Series[] + statement_id: number +} + +export interface TimeSeriesResponse { + results: Result[] +} + +export interface TimeSeriesServerResponse { + response: TimeSeriesResponse +} From b47b14f746a383880ed2bbe1f302670348a5fdee Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Sun, 29 Apr 2018 19:43:00 -0700 Subject: [PATCH 33/34] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c5ee12d3d..0c2b230a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ 1. [#3215](https://github.com/influxdata/chronograf/pull/3215): Fix Template Variables Control Bar to top of dashboard page 1. [#3214](https://github.com/influxdata/chronograf/pull/3214): Remove extra click when creating dashboard cell 1. [#3256](https://github.com/influxdata/chronograf/pull/3256): Reduce font sizes in dashboards for increased space efficiency +1. [#3245](https://github.com/influxdata/chronograf/pull/3245): Display 'no results' on cells without results ### Bug Fixes From 39af4a0244cd90268d5ec2f0ef1bc6c446d017a5 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Sun, 29 Apr 2018 20:34:50 -0700 Subject: [PATCH 34/34] Add tests for missing 'No Results' --- ui/src/shared/components/AutoRefresh.tsx | 7 +- .../shared/components/AutoRefresh.test.tsx | 74 +++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 ui/test/shared/components/AutoRefresh.test.tsx diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx index ce5a37b7f5..b1df696c09 100644 --- a/ui/src/shared/components/AutoRefresh.tsx +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -40,7 +40,7 @@ interface Template { values: TemplateValue[] } -interface Props { +export interface Props { type: string autoRefresh: number inView: boolean @@ -65,12 +65,12 @@ interface State { queryASTs?: QueryAST[] } -interface OriginalProps { +export interface OriginalProps { data: TimeSeriesServerResponse[] setResolution: (resolution: number) => void isFetchingInitially?: boolean isRefreshing?: boolean - queryASTs?: any[] + queryASTs?: QueryAST[] } const AutoRefresh = ( @@ -173,7 +173,6 @@ const AutoRefresh = ( const hasValues = _.some(timeSeries, s => { const results = _.get(s, 'response.results', []) const v = _.some(results, r => r.series) - console.error(results, v) return v }) diff --git a/ui/test/shared/components/AutoRefresh.test.tsx b/ui/test/shared/components/AutoRefresh.test.tsx new file mode 100644 index 0000000000..bf750e6684 --- /dev/null +++ b/ui/test/shared/components/AutoRefresh.test.tsx @@ -0,0 +1,74 @@ +import AutoRefresh, { + Props, + OriginalProps, +} from 'src/shared/components/AutoRefresh' +import React, {Component} from 'react' +import {shallow} from 'enzyme' + +type ComponentProps = Props & OriginalProps + +class MyComponent extends Component { + public render(): JSX.Element { + return

    Here

    + } +} + +const axes = { + bounds: { + y: [1], + y2: [2], + }, +} + +const defaultProps = { + type: 'table', + autoRefresh: 1, + inView: true, + templates: [], + queries: [], + axes, + editQueryStatus: () => {}, + grabDataForDownload: () => {}, + data: [], + setResolution: () => {}, + isFetchingInitially: false, + isRefreshing: false, + queryASTs: [], +} + +const setup = (overrides: Partial = {}) => { + const ARComponent = AutoRefresh(MyComponent) + + const props = {...defaultProps, ...overrides} + + return shallow() +} + +describe('Shared.Components.AutoRefresh', () => { + describe('render', () => { + describe('when there are no results', () => { + it('renders the no results component', () => { + const wrapped = setup() + expect(wrapped.find('.graph-empty').exists()).toBe(true) + }) + }) + + describe('when there are results', () => { + it('renderes the wrapped component', () => { + const wrapped = setup() + const timeSeries = [ + { + response: { + results: [{series: [1]}], + }, + }, + ] + wrapped.update() + wrapped.setState({timeSeries}) + process.nextTick(() => { + expect(wrapped.find(MyComponent).exists()).toBe(true) + }) + }) + }) + }) +})