diff --git a/ui/src/flux/apis/index.ts b/ui/src/flux/apis/index.ts index 65d5d5661f..e2232d360c 100644 --- a/ui/src/flux/apis/index.ts +++ b/ui/src/flux/apis/index.ts @@ -3,7 +3,10 @@ import _ from 'lodash' import AJAX from 'src/utils/ajax' import {Service, FluxTable} from 'src/types' import {updateService} from 'src/shared/apis' -import {parseResponse} from 'src/shared/parsing/flux/response' +import { + parseResponse, + parseResponseError, +} from 'src/shared/parsing/flux/response' import {MAX_RESPONSE_BYTES} from 'src/flux/constants' export const getSuggestions = async (url: string) => { @@ -56,6 +59,9 @@ export const getTimeSeries = async ( service.links.proxy }?path=/v1/query${mark}orgName=defaulorgname${and}q=${garbage}` + let responseBody: string + let responseByteLength: number + try { // We are using the `fetch` API here since the `AJAX` utility lacks support // for limiting response size. The `AJAX` utility depends on @@ -66,16 +72,28 @@ export const getTimeSeries = async ( const resp = await fetch(url, {method: 'POST'}) const {body, byteLength} = await decodeFluxRespWithLimit(resp) - return { - tables: parseResponse(body), - didTruncate: byteLength >= MAX_RESPONSE_BYTES, - } + responseBody = body + responseByteLength = byteLength } catch (error) { console.error('Problem fetching data', error) throw _.get(error, 'headers.x-influx-error', false) || _.get(error, 'data.message', 'unknown error 🤷') } + + try { + return { + tables: parseResponse(responseBody), + didTruncate: responseByteLength >= MAX_RESPONSE_BYTES, + } + } catch (error) { + console.error('Could not parse response body', error) + + return { + tables: parseResponseError(responseBody), + didTruncate: false, + } + } } export const updateScript = async (service: Service, script: string) => { diff --git a/ui/src/flux/components/ExpressionNode.tsx b/ui/src/flux/components/ExpressionNode.tsx index 8275fd6cf7..b58232698b 100644 --- a/ui/src/flux/components/ExpressionNode.tsx +++ b/ui/src/flux/components/ExpressionNode.tsx @@ -1,12 +1,12 @@ import React, {PureComponent, Fragment} from 'react' -import _ from 'lodash' import {FluxContext} from 'src/flux/containers/FluxPage' import FuncSelector from 'src/flux/components/FuncSelector' import FuncNode from 'src/flux/components/FuncNode' import YieldFuncNode from 'src/flux/components/YieldFuncNode' +import {getDeep} from 'src/utils/wrappers' -import {Func} from 'src/types/flux' +import {Func, Context} from 'src/types/flux' interface Props { funcNames: any[] @@ -22,7 +22,6 @@ interface State { nonYieldableIndexesToggled: { [x: number]: boolean } - isImplicitYieldToggled: boolean } // an Expression is a group of one or more functions @@ -32,7 +31,6 @@ class ExpressionNode extends PureComponent { this.state = { nonYieldableIndexesToggled: {}, - isImplicitYieldToggled: this.isImplicitYieldToggled, } } @@ -59,13 +57,17 @@ class ExpressionNode extends PureComponent { service, data, scriptUpToYield, - }) => { + }: Context) => { let isAfterRange = false let isAfterFilter = false return ( <> {funcs.map((func, i) => { + if (func.name === 'yield') { + return null + } + if (func.name === 'range') { isAfterRange = true } @@ -74,22 +76,7 @@ class ExpressionNode extends PureComponent { isAfterFilter = true } - if (func.name === 'yield') { - const script = scriptUpToYield(bodyID, declarationID, i, true) - - return ( - - ) - } + const isYieldable = isAfterFilter && isAfterRange const funcNode = ( { onChangeArg={onChangeArg} onDelete={onDeleteFuncNode} onToggleYield={onToggleYield} - isYieldable={isAfterFilter && isAfterRange} - isYielding={this.isBeforeFuncYield(i)} + isYieldable={isYieldable} + isYielding={this.isBeforeYielding(i)} + isYieldedInScript={this.isYieldNodeIndex(i + 1)} declarationID={declarationID} onGenerateScript={onGenerateScript} declarationsFromBody={declarationsFromBody} @@ -112,12 +100,15 @@ class ExpressionNode extends PureComponent { /> ) - if (nonYieldableIndexesToggled[i]) { - const script = scriptUpToYield( + if ( + nonYieldableIndexesToggled[i] || + this.isYieldNodeIndex(i + 1) + ) { + const script: string = scriptUpToYield( bodyID, declarationID, i, - false + isYieldable ) return ( @@ -134,43 +125,9 @@ class ExpressionNode extends PureComponent { /> ) - } else if (this.isEndOfScript(i)) { - const script = scriptUpToYield(bodyID, declarationID, i, true) - - return ( - - - - - ) - } else { - return funcNode } + + return funcNode })} { ) } - private isBeforeFuncYield(funcIndex: number): boolean { - const {funcs, isLastBody} = this.props - const {isImplicitYieldToggled} = this.state + private isBeforeYielding(funcIndex: number): boolean { + const {nonYieldableIndexesToggled} = this.state + const beforeToggledLastYield = !!nonYieldableIndexesToggled[funcIndex] - if ( - funcIndex === funcs.length - 1 && - isLastBody && - isImplicitYieldToggled - ) { + if (beforeToggledLastYield) { return true } - if (funcIndex === funcs.length - 1) { - return false - } - - const nextFunc = funcs[funcIndex + 1] - - if (nextFunc.name === 'yield') { - return true - } - - return false + return this.isYieldNodeIndex(funcIndex + 1) } - private get isImplicitYieldToggled(): boolean { - const {isLastBody} = this.props - - return isLastBody && this.isLastFuncYield - } - - private get isLastFuncYield(): boolean { + private isYieldNodeIndex(funcIndex: number): boolean { const {funcs} = this.props + const funcName = getDeep(funcs, `${funcIndex}.name`, '') - return _.get(funcs, `${funcs.length - 1}.name`) !== 'yield' + return funcName === 'yield' } // if funcNode is not yieldable, add last before yield() - private handleToggleYieldWithLast = (funcNodeIndex: number) => { + private handleToggleYieldWithLast = (funcNodeIndex: number): void => { this.setState(({nonYieldableIndexesToggled}) => { const isFuncYieldToggled = !!nonYieldableIndexesToggled[funcNodeIndex] @@ -235,20 +173,6 @@ class ExpressionNode extends PureComponent { } }) } - - private handleHideImplicitYield = () => { - this.setState(() => ({ - isImplicitYieldToggled: false, - })) - } - - private isEndOfScript(index: number): boolean { - const {isLastBody, funcs} = this.props - const {isImplicitYieldToggled} = this.state - const isLastScriptFunc = isLastBody && index === funcs.length - 1 - - return isLastScriptFunc && isImplicitYieldToggled - } } export default ExpressionNode diff --git a/ui/src/flux/components/FuncArg.tsx b/ui/src/flux/components/FuncArg.tsx index 06eb8f2fec..b02c9e4f1e 100644 --- a/ui/src/flux/components/FuncArg.tsx +++ b/ui/src/flux/components/FuncArg.tsx @@ -8,7 +8,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors' import FromDatabaseDropdown from 'src/flux/components/FromDatabaseDropdown' import {funcNames, argTypes} from 'src/flux/constants' -import {OnChangeArg, Arg} from 'src/types/flux' +import {OnChangeArg, Arg, OnGenerateScript} from 'src/types/flux' import {Service} from 'src/types' interface Props { @@ -22,7 +22,7 @@ interface Props { bodyID: string declarationID: string onChangeArg: OnChangeArg - onGenerateScript: () => void + onGenerateScript: OnGenerateScript } @ErrorHandling diff --git a/ui/src/flux/components/FuncArgInput.tsx b/ui/src/flux/components/FuncArgInput.tsx index eb6ce7622b..e649e7c2e0 100644 --- a/ui/src/flux/components/FuncArgInput.tsx +++ b/ui/src/flux/components/FuncArgInput.tsx @@ -1,6 +1,6 @@ import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react' import {ErrorHandling} from 'src/shared/decorators/errors' -import {OnChangeArg} from 'src/types/flux' +import {OnChangeArg, OnGenerateScript} from 'src/types/flux' interface Props { funcID: string @@ -10,7 +10,7 @@ interface Props { bodyID: string declarationID: string onChangeArg: OnChangeArg - onGenerateScript: () => void + onGenerateScript: OnGenerateScript autoFocus?: boolean } diff --git a/ui/src/flux/components/FuncArgs.tsx b/ui/src/flux/components/FuncArgs.tsx index e01aca54aa..bcc72c9be9 100644 --- a/ui/src/flux/components/FuncArgs.tsx +++ b/ui/src/flux/components/FuncArgs.tsx @@ -2,7 +2,7 @@ import React, {PureComponent, ReactElement, MouseEvent} from 'react' import FuncArg from 'src/flux/components/FuncArg' import {OnChangeArg} from 'src/types/flux' import {ErrorHandling} from 'src/shared/decorators/errors' -import {Func} from 'src/types/flux' +import {Func, OnGenerateScript} from 'src/types/flux' import {funcNames} from 'src/flux/constants' import JoinArgs from 'src/flux/components/JoinArgs' import FilterArgs from 'src/flux/components/FilterArgs' @@ -15,7 +15,7 @@ interface Props { bodyID: string onChangeArg: OnChangeArg declarationID: string - onGenerateScript: () => void + onGenerateScript: OnGenerateScript declarationsFromBody: string[] onStopPropagation: (e: MouseEvent) => void } diff --git a/ui/src/flux/components/FuncNode.tsx b/ui/src/flux/components/FuncNode.tsx index 0eeefd0762..fc2607577e 100644 --- a/ui/src/flux/components/FuncNode.tsx +++ b/ui/src/flux/components/FuncNode.tsx @@ -6,6 +6,7 @@ import BodyDelete from 'src/flux/components/BodyDelete' import FuncArgs from 'src/flux/components/FuncArgs' import FuncArgsPreview from 'src/flux/components/FuncArgsPreview' import { + OnGenerateScript, OnDeleteFuncNode, OnChangeArg, OnToggleYield, @@ -24,12 +25,13 @@ interface Props { onDelete: OnDeleteFuncNode onToggleYield: OnToggleYield onChangeArg: OnChangeArg - onGenerateScript: () => void + onGenerateScript: OnGenerateScript onToggleYieldWithLast: (funcNodeIndex: number) => void declarationsFromBody: string[] isYielding: boolean isYieldable: boolean onDeleteBody: (bodyID: string) => void + isYieldedInScript: boolean } interface State { @@ -182,11 +184,12 @@ export default class FuncNode extends PureComponent { index, bodyID, declarationID, - isYieldable, onToggleYieldWithLast, + isYieldable, + isYieldedInScript, } = this.props - if (isYieldable) { + if (isYieldedInScript || isYieldable) { onToggleYield(bodyID, declarationID, index) } else { onToggleYieldWithLast(index) diff --git a/ui/src/flux/containers/FluxPage.tsx b/ui/src/flux/containers/FluxPage.tsx index 24be296328..59b57431a7 100644 --- a/ui/src/flux/containers/FluxPage.tsx +++ b/ui/src/flux/containers/FluxPage.tsx @@ -344,7 +344,7 @@ export class FluxPage extends PureComponent { declarationID: string, funcNodeIndex: number, isYieldable: boolean - ) => { + ): string => { const {body: bodies} = this.state const bodyIndex = bodies.findIndex(b => b.id === bodyID) diff --git a/ui/src/shared/parsing/flux/response.ts b/ui/src/shared/parsing/flux/response.ts index 5f9f369a67..634ebb5e9a 100644 --- a/ui/src/shared/parsing/flux/response.ts +++ b/ui/src/shared/parsing/flux/response.ts @@ -4,6 +4,19 @@ import uuid from 'uuid' import {FluxTable} from 'src/types' +export const parseResponseError = (response: string): FluxTable[] => { + const data = Papa.parse(response.trim()).data as string[][] + + return [ + { + id: uuid.v4(), + name: 'Error', + partitionKey: {}, + data, + }, + ] +} + export const parseResponse = (response: string): FluxTable[] => { const trimmedReponse = response.trim() diff --git a/ui/src/types/flux.ts b/ui/src/types/flux.ts index ccfae353f1..73250b3f34 100644 --- a/ui/src/types/flux.ts +++ b/ui/src/types/flux.ts @@ -12,7 +12,7 @@ export type OnToggleYield = ( declarationID: string, funcNodeIndex: number ) => void -export type OnGenerateScript = (script: string) => void +export type OnGenerateScript = () => void export type OnChangeScript = (script: string) => void export type OnSubmitScript = () => void export type ScriptUpToYield = (