diff --git a/chronograf/ui/src/dataExplorer/components/DataExplorer.scss b/chronograf/ui/src/dataExplorer/components/DataExplorer.scss new file mode 100644 index 0000000000..7b9c34b56a --- /dev/null +++ b/chronograf/ui/src/dataExplorer/components/DataExplorer.scss @@ -0,0 +1,3 @@ +.data-explorer { + padding: 32px; +} diff --git a/chronograf/ui/src/dataExplorer/components/DataExplorer.tsx b/chronograf/ui/src/dataExplorer/components/DataExplorer.tsx index 1f93be75c1..3651aa08b3 100644 --- a/chronograf/ui/src/dataExplorer/components/DataExplorer.tsx +++ b/chronograf/ui/src/dataExplorer/components/DataExplorer.tsx @@ -1,19 +1,49 @@ -import React, {PureComponent} from 'react' +// Libraries +import React, {PureComponent, ComponentClass} from 'react' +import {connect} from 'react-redux' -interface Props {} +// Components +import TimeMachine from 'src/shared/components/TimeMachine' + +// Actions +import {setActiveTimeMachineID} from 'src/shared/actions/v2/timeMachines' + +// Utils +import {DE_TIME_MACHINE_ID} from 'src/shared/constants/timeMachine' + +interface StateProps {} + +interface DispatchProps { + onSetActiveTimeMachineID: typeof setActiveTimeMachineID +} + +interface PassedProps {} interface State {} -class DataExplorer extends PureComponent { - constructor(props) { - super(props) +type Props = StateProps & DispatchProps & PassedProps - this.state = {} +class DataExplorer extends PureComponent { + public componentDidMount() { + const {onSetActiveTimeMachineID} = this.props + + onSetActiveTimeMachineID(DE_TIME_MACHINE_ID) } public render() { - return null + return ( +
+ +
+ ) } } -export default DataExplorer +const mdtp: DispatchProps = { + onSetActiveTimeMachineID: setActiveTimeMachineID, +} + +export default connect(null, mdtp)(DataExplorer) as ComponentClass< + PassedProps, + State +> diff --git a/chronograf/ui/src/flux/actions/index.ts b/chronograf/ui/src/flux/actions/index.ts deleted file mode 100644 index 8f5375b512..0000000000 --- a/chronograf/ui/src/flux/actions/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type Action = ActionUpdateScript - -export enum ActionTypes { - UpdateScript = 'UPDATE_SCRIPT', -} - -export interface ActionUpdateScript { - type: ActionTypes.UpdateScript - payload: { - script: string - } -} - -export type UpdateScript = (script: string) => ActionUpdateScript - -export const updateScript = (script: string): ActionUpdateScript => { - return { - type: ActionTypes.UpdateScript, - payload: {script}, - } -} diff --git a/chronograf/ui/src/flux/components/BodyBuilder.tsx b/chronograf/ui/src/flux/components/BodyBuilder.tsx deleted file mode 100644 index 9b6f8b5dbc..0000000000 --- a/chronograf/ui/src/flux/components/BodyBuilder.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, {PureComponent} from 'react' -import _ from 'lodash' - -import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar' -import ExpressionNode from 'src/flux/components/ExpressionNode' -import VariableNode from 'src/flux/components/VariableNode' -import FuncSelector from 'src/flux/components/FuncSelector' -import BodyDelete from 'src/flux/components/BodyDelete' -import {funcNames} from 'src/flux/constants' - -import {Body, Suggestion} from 'src/types/flux' - -interface Props { - body: Body[] - suggestions: Suggestion[] - onAppendFrom: () => void - onAppendJoin: () => void - onDeleteBody: (bodyID: string) => void -} - -class BodyBuilder extends PureComponent { - public render() { - const {body, onDeleteBody} = this.props - - const bodybuilder = body.map((b, i) => { - if (b.declarations.length) { - return b.declarations.map(d => { - if (d.funcs) { - return ( -
-
- -
- -
-
- -
- ) - } - - return ( -
-
- -
- -
-
-
- ) - }) - } - - return ( -
- -
- ) - }) - - return ( - -
- {_.flatten(bodybuilder)} -
- -
-
-
- ) - } - - private isLastBody(bodyIndex: number): boolean { - const {body} = this.props - - return bodyIndex === body.length - 1 - } - - private get newDeclarationFuncs(): string[] { - const {body} = this.props - const declarationFunctions = [funcNames.FROM] - if (body.length > 1) { - declarationFunctions.push(funcNames.JOIN) - } - return declarationFunctions - } - - private get declarationsFromBody(): string[] { - const {body} = this.props - const declarations = _.flatten( - body.map(b => { - if ('declarations' in b) { - const declarationsArray = b.declarations - return declarationsArray.map(da => da.name) - } - return [] - }) - ) - return declarations - } - - private handleCreateNewBody = name => { - if (name === funcNames.FROM) { - this.props.onAppendFrom() - } - if (name === funcNames.JOIN) { - this.props.onAppendJoin() - } - } - - private get funcNames() { - return this.props.suggestions.map(f => f.name) - } -} - -export default BodyBuilder diff --git a/chronograf/ui/src/flux/components/BodyDelete.tsx b/chronograf/ui/src/flux/components/BodyDelete.tsx deleted file mode 100644 index d5f6c0461b..0000000000 --- a/chronograf/ui/src/flux/components/BodyDelete.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, {PureComponent} from 'react' -import ConfirmButton from 'src/shared/components/ConfirmButton' - -type BodyType = 'variable' | 'query' - -interface Props { - bodyID: string - type?: BodyType - onDeleteBody: (bodyID: string) => void -} - -class BodyDelete extends PureComponent { - public static defaultProps: Partial = { - type: 'query', - } - - public render() { - const {type} = this.props - - if (type === 'variable') { - return ( - - ) - } - - return ( - - ) - } - - private handleDelete = (): void => { - this.props.onDeleteBody(this.props.bodyID) - } -} - -export default BodyDelete diff --git a/chronograf/ui/src/flux/components/DatabaseList.tsx b/chronograf/ui/src/flux/components/DatabaseList.tsx deleted file mode 100644 index ee85f55f10..0000000000 --- a/chronograf/ui/src/flux/components/DatabaseList.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// Libraries -import React, {PureComponent} from 'react' -import _ from 'lodash' - -// Components -import DatabaseListItem from 'src/flux/components/DatabaseListItem' - -// APIs -import {getBuckets} from 'src/shared/apis/v2/buckets' - -// Types -import {Source} from 'src/types/v2' -import {NotificationAction} from 'src/types/notifications' - -import {ErrorHandling} from 'src/shared/decorators/errors' - -interface Props { - source: Source - notify: NotificationAction -} - -interface State { - databases: string[] -} - -@ErrorHandling -class DatabaseList extends PureComponent { - constructor(props) { - super(props) - - this.state = { - databases: [], - } - } - - public componentDidMount() { - this.getDatabases() - } - - public async getDatabases() { - const {source} = this.props - - try { - const buckets = await getBuckets(source.links.buckets) - const sorted = _.sortBy(buckets, b => b.name.toLocaleLowerCase()) - const databases = sorted.map(db => db.name) - - this.setState({databases}) - } catch (err) { - console.error(err) - } - } - - public render() { - const {databases} = this.state - const {source, notify} = this.props - - return databases.map(db => { - return ( - - ) - }) - } -} - -export default DatabaseList diff --git a/chronograf/ui/src/flux/components/DatabaseListItem.tsx b/chronograf/ui/src/flux/components/DatabaseListItem.tsx deleted file mode 100644 index eeed843430..0000000000 --- a/chronograf/ui/src/flux/components/DatabaseListItem.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, {PureComponent, ChangeEvent, MouseEvent} from 'react' -import classnames from 'classnames' -import {CopyToClipboard} from 'react-copy-to-clipboard' - -import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries' -import parseValuesColumn from 'src/shared/parsing/flux/values' -import TagList from 'src/flux/components/TagList' -import { - copyToClipboardSuccess, - copyToClipboardFailed, -} from 'src/shared/copy/notifications' - -import {NotificationAction} from 'src/types' -import {Source} from 'src/types/v2' - -interface Props { - db: string - source: Source - notify: NotificationAction -} - -interface State { - isOpen: boolean - tags: string[] - searchTerm: string -} - -class DatabaseListItem extends PureComponent { - constructor(props) { - super(props) - this.state = { - isOpen: false, - tags: [], - searchTerm: '', - } - } - - public async componentDidMount() { - const {db, source} = this.props - - try { - const response = await fetchTagKeys(source, db, []) - const tags = parseValuesColumn(response) - this.setState({tags}) - } catch (error) { - console.error(error) - } - } - - public render() { - const {db} = this.props - - return ( -
-
-
-
- {db} - Bucket -
- -
- - Copy -
-
-
- {this.filterAndTagList} -
- ) - } - - private get tags(): string[] { - const {tags, searchTerm} = this.state - const term = searchTerm.toLocaleLowerCase() - return tags.filter(t => t.toLocaleLowerCase().includes(term)) - } - - private get className(): string { - return classnames('flux-schema-tree', { - expanded: this.state.isOpen, - }) - } - - private get filterAndTagList(): JSX.Element { - const {db, source, notify} = this.props - const {isOpen, searchTerm} = this.state - - return ( -
-
- -
- -
- ) - } - - private handleClickCopy = e => { - e.stopPropagation() - } - - private handleCopyAttempt = ( - copiedText: string, - isSuccessful: boolean - ): void => { - const {notify} = this.props - if (isSuccessful) { - notify(copyToClipboardSuccess(copiedText)) - } else { - notify(copyToClipboardFailed(copiedText)) - } - } - - private onSearch = (e: ChangeEvent) => { - this.setState({ - searchTerm: e.target.value, - }) - } - - private handleClick = () => { - this.setState({isOpen: !this.state.isOpen}) - } - - private handleInputClick = (e: MouseEvent) => { - e.stopPropagation() - } -} - -export default DatabaseListItem diff --git a/chronograf/ui/src/flux/components/ExpressionNode.tsx b/chronograf/ui/src/flux/components/ExpressionNode.tsx deleted file mode 100644 index 449adfde88..0000000000 --- a/chronograf/ui/src/flux/components/ExpressionNode.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, {PureComponent, Fragment} from 'react' - -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, Context} from 'src/types/flux' - -interface Props { - funcNames: any[] - bodyID: string - funcs: Func[] - declarationID?: string - declarationsFromBody: string[] - isLastBody: boolean - onDeleteBody: (bodyID: string) => void -} - -interface State { - nonYieldableIndexesToggled: { - [x: number]: boolean - } -} - -// an Expression is a group of one or more functions -class ExpressionNode extends PureComponent { - constructor(props: Props) { - super(props) - - this.state = { - nonYieldableIndexesToggled: {}, - } - } - - public render() { - const { - declarationID, - bodyID, - funcNames, - funcs, - declarationsFromBody, - onDeleteBody, - } = this.props - - const {nonYieldableIndexesToggled} = this.state - - return ( - - {({ - onDeleteFuncNode, - onAddNode, - onChangeArg, - onGenerateScript, - onToggleYield, - source, - 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 - } - - if (func.name === 'filter') { - isAfterFilter = true - } - const isYieldable = isAfterFilter && isAfterRange - - const funcNode = ( - - ) - - if ( - nonYieldableIndexesToggled[i] || - this.isYieldNodeIndex(i + 1) - ) { - const script: string = scriptUpToYield( - bodyID, - declarationID, - i, - isYieldable - ) - - let yieldFunc = func - - if (this.isYieldNodeIndex(i + 1)) { - yieldFunc = funcs[i + 1] - } - - return ( - - {funcNode} - - - ) - } - - return funcNode - })} - - - ) - }} - - ) - } - - private isBeforeYielding(funcIndex: number): boolean { - const {nonYieldableIndexesToggled} = this.state - const beforeToggledLastYield = !!nonYieldableIndexesToggled[funcIndex] - - if (beforeToggledLastYield) { - return true - } - - return this.isYieldNodeIndex(funcIndex + 1) - } - - private isYieldNodeIndex(funcIndex: number): boolean { - const {funcs} = this.props - const funcName = getDeep(funcs, `${funcIndex}.name`, '') - - return funcName === 'yield' - } - - // if funcNode is not yieldable, add last before yield() - private handleToggleYieldWithLast = (funcNodeIndex: number): void => { - this.setState(({nonYieldableIndexesToggled}) => { - const isFuncYieldToggled = !!nonYieldableIndexesToggled[funcNodeIndex] - - return { - nonYieldableIndexesToggled: { - ...nonYieldableIndexesToggled, - [funcNodeIndex]: !isFuncYieldToggled, - }, - } - }) - } -} - -export default ExpressionNode diff --git a/chronograf/ui/src/flux/components/FilterArgs.tsx b/chronograf/ui/src/flux/components/FilterArgs.tsx deleted file mode 100644 index 88431efa94..0000000000 --- a/chronograf/ui/src/flux/components/FilterArgs.tsx +++ /dev/null @@ -1,129 +0,0 @@ -// Libraries -import React, {PureComponent} from 'react' -import {connect} from 'react-redux' - -// Components -import FilterTagList from 'src/flux/components/FilterTagList' - -// APIs -import {getAST} from 'src/flux/apis' -import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries' - -// Utils -import parseValuesColumn from 'src/shared/parsing/flux/values' -import Walker from 'src/flux/ast/walker' -import {makeCancelable} from 'src/utils/promises' - -// Types -import {Source} from 'src/types/v2' -import {Links, OnChangeArg, Func, FilterNode} from 'src/types/flux' -import {WrappedCancelablePromise} from 'src/types/promises' - -interface Props { - links: Links - value: string - func: Func - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - db: string - source: Source - onGenerateScript: () => void -} - -interface State { - tagKeys: string[] - nodes: FilterNode[] - ast: object -} - -class FilterArgs extends PureComponent { - private fetchTagKeysResponse?: WrappedCancelablePromise - - constructor(props) { - super(props) - this.state = { - tagKeys: [], - nodes: [], - ast: {}, - } - } - - public async convertStringToNodes() { - const {links, value} = this.props - - const ast = await getAST({url: links.ast, query: value}) - const nodes = new Walker(ast).inOrderExpression - this.setState({nodes, ast}) - } - - public componentDidUpdate(prevProps) { - if (prevProps.value !== this.props.value) { - this.convertStringToNodes() - } - } - - public async componentDidMount() { - try { - this.convertStringToNodes() - const response = await this.getTagKeys() - const tagKeys = parseValuesColumn(response) - - this.setState({ - tagKeys, - }) - } catch (error) { - if (!error.isCanceled) { - console.error(error) - } - } - } - - public componentWillUnmount() { - if (this.fetchTagKeysResponse) { - this.fetchTagKeysResponse.cancel() - } - } - - public render() { - const { - db, - source, - onChangeArg, - func, - bodyID, - declarationID, - onGenerateScript, - } = this.props - const {nodes} = this.state - - return ( - - ) - } - - private getTagKeys(): Promise { - const {db, source} = this.props - - this.fetchTagKeysResponse = makeCancelable(fetchTagKeys(source, db, [])) - - return this.fetchTagKeysResponse.promise - } -} - -const mapStateToProps = ({links}) => { - return {links: links.query} -} - -export default connect(mapStateToProps, null)(FilterArgs) diff --git a/chronograf/ui/src/flux/components/FilterBuilder.tsx b/chronograf/ui/src/flux/components/FilterBuilder.tsx deleted file mode 100644 index 78a8a08101..0000000000 --- a/chronograf/ui/src/flux/components/FilterBuilder.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, {PureComponent} from 'react' -import {MemberExpressionNode} from 'src/types/flux' - -type FilterNode = MemberExpressionNode - -interface Props { - nodes: FilterNode[] -} - -interface State { - tags: Tags -} - -interface Tags { - [x: string]: string[] -} - -export class FilterBuilder extends PureComponent { - constructor(props) { - super(props) - this.state = { - tags: this.tags, - } - } - - public render() { - return
Filter Builder
- } - - private get tags(): Tags { - const {nodes} = this.props - return nodes.reduce((acc, node, i) => { - if (node.type === 'MemberExpression') { - const tagKey = node.property.name - const remainingNodes = nodes.slice(i + 1, nodes.length) - const tagValue = remainingNodes.find(n => { - return n.type !== 'Operator' - }) - - if (!(tagKey in acc)) { - return {...acc, [tagKey]: [tagValue.source]} - } - - return {...acc, [tagKey]: [...acc[tagKey], tagValue.source]} - } - - return acc - }, {}) - } -} - -export default FilterBuilder diff --git a/chronograf/ui/src/flux/components/FilterConditionNode.tsx b/chronograf/ui/src/flux/components/FilterConditionNode.tsx deleted file mode 100644 index a41b0c9221..0000000000 --- a/chronograf/ui/src/flux/components/FilterConditionNode.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, {PureComponent} from 'react' -import {FilterNode, MemberExpressionNode} from 'src/types/flux' - -interface Props { - node: FilterNode -} - -class FilterConditionNode extends PureComponent { - public render() { - const {node} = this.props - - switch (node.type) { - case 'ObjectExpression': { - return
{node.source}
- } - case 'MemberExpression': { - const memberNode = node as MemberExpressionNode - return ( -
{memberNode.property.name}
- ) - } - case 'OpenParen': { - return
- } - case 'CloseParen': { - return
- } - case 'NumberLiteral': - case 'IntegerLiteral': { - return
{node.source}
- } - case 'BooleanLiteral': { - return
{node.source}
- } - case 'StringLiteral': { - return
{node.source}
- } - case 'Operator': { - return
{node.source}
- } - default: { - return
- } - } - } -} - -export default FilterConditionNode diff --git a/chronograf/ui/src/flux/components/FilterConditions.tsx b/chronograf/ui/src/flux/components/FilterConditions.tsx deleted file mode 100644 index 7877a0f643..0000000000 --- a/chronograf/ui/src/flux/components/FilterConditions.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, {PureComponent} from 'react' -import {FilterNode} from 'src/types/flux' -import FilterConditionNode from 'src/flux/components/FilterConditionNode' - -interface Props { - nodes: FilterNode[] -} - -class FilterConditions extends PureComponent { - public render() { - return ( - <> - {this.props.nodes.map((n, i) => ( - - ))} - - ) - } -} - -export default FilterConditions diff --git a/chronograf/ui/src/flux/components/FilterPreview.tsx b/chronograf/ui/src/flux/components/FilterPreview.tsx deleted file mode 100644 index 76306d9c31..0000000000 --- a/chronograf/ui/src/flux/components/FilterPreview.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, {PureComponent} from 'react' -import {connect} from 'react-redux' -import {getAST} from 'src/flux/apis' -import {Links, FilterNode} from 'src/types/flux' -import Walker from 'src/flux/ast/walker' -import FilterConditions from 'src/flux/components/FilterConditions' - -interface Props { - filterString?: string - links: Links -} - -interface State { - nodes: FilterNode[] - ast: object -} - -export class FilterPreview extends PureComponent { - public static defaultProps: Partial = { - filterString: '', - } - - constructor(props) { - super(props) - this.state = { - nodes: [], - ast: {}, - } - } - - public async componentDidMount() { - this.convertStringToNodes() - } - - public async componentDidUpdate(prevProps, __) { - if (this.props.filterString !== prevProps.filterString) { - this.convertStringToNodes() - } - } - - public async convertStringToNodes() { - const {links, filterString} = this.props - - const ast = await getAST({url: links.ast, query: filterString}) - const nodes = new Walker(ast).inOrderExpression - this.setState({nodes, ast}) - } - - public render() { - return - } -} - -const mapStateToProps = ({links}) => { - return {links: links.query} -} - -export default connect(mapStateToProps, null)(FilterPreview) diff --git a/chronograf/ui/src/flux/components/FilterTagList.test.tsx b/chronograf/ui/src/flux/components/FilterTagList.test.tsx deleted file mode 100644 index 3cfb622447..0000000000 --- a/chronograf/ui/src/flux/components/FilterTagList.test.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import React from 'react' -import {shallow} from 'enzyme' -import FilterTagList from 'src/flux/components/FilterTagList' -import FilterTagListItem from 'src/flux/components/FilterTagListItem' - -const setup = (override?) => { - const props = { - db: 'telegraf', - tags: ['cpu', '_measurement'], - filter: [], - func: { - id: 'f1', - args: [{key: 'fn', value: '(r) => true'}], - }, - nodes: [], - bodyID: 'b1', - declarationID: 'd1', - onChangeArg: () => {}, - onGenerateScript: () => {}, - ...override, - } - - const wrapper = shallow() - - return { - wrapper, - props, - } -} - -describe('Flux.Components.FilterTagList', () => { - describe('rendering', () => { - it('renders without errors', () => { - const {wrapper} = setup() - - expect(wrapper.exists()).toBe(true) - }) - }) - - it('renders a builder when the clause is parseable', () => { - const override = { - nodes: [{type: 'BooleanLiteral', source: 'true'}], - } - const {wrapper} = setup(override) - - const builderContents = wrapper.find(FilterTagListItem) - expect(builderContents).not.toHaveLength(0) - }) - - it('renders a builder when the clause cannot be parsed', () => { - const override = { - nodes: [{type: 'Unparseable', source: 'baconcannon'}], - } - const {wrapper} = setup(override) - - const builderContents = wrapper.find(FilterTagListItem) - expect(builderContents).toHaveLength(0) - }) - - describe('clause parseability', () => { - const parser = setup().wrapper.instance() as FilterTagList - - it('recognizes a simple `true` body', () => { - const nodes = [{type: 'BooleanLiteral', source: 'true'}] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({}) - }) - - it('allows for an empty node list', () => { - const nodes = [] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({}) - }) - - it('extracts a tag condition equality', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '=='}, - {type: 'StringLiteral', source: 'tagValue'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({ - tagKey: [{key: 'tagKey', operator: '==', value: 'tagValue'}], - }) - }) - - it('extracts a tag condition inequality', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'tagValue'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({ - tagKey: [{key: 'tagKey', operator: '!=', value: 'tagValue'}], - }) - }) - - it('groups like keys together', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'value1'}, - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'value2'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({ - tagKey: [ - {key: 'tagKey', operator: '!=', value: 'value1'}, - {key: 'tagKey', operator: '!=', value: 'value2'}, - ], - }) - }) - - it('separates conditions with different keys', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'key1'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'value1'}, - {type: 'MemberExpression', property: {name: 'key2'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'value2'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(true) - expect(clause).toEqual({ - key1: [{key: 'key1', operator: '!=', value: 'value1'}], - key2: [{key: 'key2', operator: '!=', value: 'value2'}], - }) - }) - - it('cannot recognize other operators', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '=~'}, - {type: 'StringLiteral', source: 'tagValue'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(false) - expect(clause).toEqual({}) - }) - - it('requires that operators be consistent within a key group', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '=='}, - {type: 'StringLiteral', source: 'tagValue'}, - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'Operator', source: '!='}, - {type: 'StringLiteral', source: 'tagValue'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(false) - expect(clause).toEqual({}) - }) - - it('conditions must come in order to be recognizeable', () => { - const nodes = [ - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'StringLiteral', source: 'tagValue'}, - {type: 'Operator', source: '=~'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(false) - expect(clause).toEqual({}) - }) - - it('does not recognize more esoteric types', () => { - const nodes = [ - {type: 'ArrayExpression', property: {name: 'tagKey'}}, - {type: 'MemberExpression', property: {name: 'tagKey'}}, - {type: 'StringLiteral', source: 'tagValue'}, - {type: 'Operator', source: '=~'}, - ] - const [clause, parseable] = parser.reduceNodesToClause(nodes, []) - - expect(parseable).toBe(false) - expect(clause).toEqual({}) - }) - }) - - describe('building a filter string', () => { - const builder = setup().wrapper.instance() as FilterTagList - - it('returns a simple filter with no conditions', () => { - const filterString = builder.buildFilterString({}) - expect(filterString).toEqual('() => true') - }) - - it('renders a single condition', () => { - const clause = { - myKey: [{key: 'myKey', operator: '==', value: 'val1'}], - } - const filterString = builder.buildFilterString(clause) - expect(filterString).toEqual('(r) => (r.myKey == "val1")') - }) - - it('groups like keys together', () => { - const clause = { - myKey: [ - {key: 'myKey', operator: '==', value: 'val1'}, - {key: 'myKey', operator: '==', value: 'val2'}, - ], - } - const filterString = builder.buildFilterString(clause) - expect(filterString).toEqual( - '(r) => (r.myKey == "val1" OR r.myKey == "val2")' - ) - }) - - it('joins conditions together with AND when operator is !=', () => { - const clause = { - myKey: [ - {key: 'myKey', operator: '!=', value: 'val1'}, - {key: 'myKey', operator: '!=', value: 'val2'}, - ], - } - const filterString = builder.buildFilterString(clause) - expect(filterString).toEqual( - '(r) => (r.myKey != "val1" AND r.myKey != "val2")' - ) - }) - - it('always uses AND to join conditions across keys', () => { - const clause = { - key1: [ - {key: 'key1', operator: '!=', value: 'val1'}, - {key: 'key1', operator: '!=', value: 'val2'}, - ], - key2: [ - {key: 'key2', operator: '==', value: 'val3'}, - {key: 'key2', operator: '==', value: 'val4'}, - ], - } - const filterString = builder.buildFilterString(clause) - expect(filterString).toEqual( - '(r) => (r.key1 != "val1" AND r.key1 != "val2") AND (r.key2 == "val3" OR r.key2 == "val4")' - ) - }) - }) -}) diff --git a/chronograf/ui/src/flux/components/FilterTagList.tsx b/chronograf/ui/src/flux/components/FilterTagList.tsx deleted file mode 100644 index 33cd7713e9..0000000000 --- a/chronograf/ui/src/flux/components/FilterTagList.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import React, {PureComponent, MouseEvent} from 'react' -import _ from 'lodash' - -import { - OnChangeArg, - Func, - FilterClause, - FilterTagCondition, - FilterNode, -} from 'src/types/flux' -import {argTypes} from 'src/flux/constants' - -import FuncArgTextArea from 'src/flux/components/FuncArgTextArea' -import FilterTagListItem from 'src/flux/components/FilterTagListItem' -import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar' -import {getDeep} from 'src/utils/wrappers' - -import {SchemaFilter} from 'src/types' -import {Source} from 'src/types/v2' - -interface Props { - db: string - source: Source - tags: string[] - filter: SchemaFilter[] - onChangeArg: OnChangeArg - func: Func - nodes: FilterNode[] - bodyID: string - declarationID: string - onGenerateScript: () => void -} - -type ParsedClause = [FilterClause, boolean] - -export default class FilterTagList extends PureComponent { - public get clauseIsParseable(): boolean { - const [, parseable] = this.reduceNodesToClause(this.props.nodes, []) - return parseable - } - - public get clause(): FilterClause { - const [clause] = this.reduceNodesToClause(this.props.nodes, []) - return clause - } - - public conditions(key: string, clause?): FilterTagCondition[] { - clause = clause || this.clause - return clause[key] || [] - } - - public operator(key: string, clause?): string { - const conditions = this.conditions(key, clause) - return getDeep(conditions, '0.operator', '==') - } - - public addCondition(condition: FilterTagCondition): FilterClause { - const conditions = this.conditions(condition.key) - return { - ...this.clause, - [condition.key]: [...conditions, condition], - } - } - - public removeCondition(condition: FilterTagCondition): FilterClause { - const conditions = this.conditions(condition.key) - const newConditions = _.reject(conditions, c => _.isEqual(c, condition)) - return { - ...this.clause, - [condition.key]: newConditions, - } - } - - public buildFilterString(clause: FilterClause): string { - const funcBody = Object.entries(clause) - .filter(([__, conditions]) => conditions.length) - .map(([key, conditions]) => { - const joiner = this.operator(key, clause) === '==' ? ' OR ' : ' AND ' - const subClause = conditions - .map(c => `r.${key} ${c.operator} "${c.value}"`) - .join(joiner) - return '(' + subClause + ')' - }) - .join(' AND ') - return funcBody ? `(r) => ${funcBody}` : `() => true` - } - - public handleChangeValue = ( - key: string, - value: string, - selected: boolean - ): void => { - const condition: FilterTagCondition = { - key, - operator: this.operator(key), - value, - } - const clause: FilterClause = selected - ? this.addCondition(condition) - : this.removeCondition(condition) - const filterString: string = this.buildFilterString(clause) - this.updateFilterString(filterString) - } - - public handleSetEquality = (key: string, equal: boolean): void => { - const operator = equal ? '==' : '!=' - const clause: FilterClause = { - ...this.clause, - [key]: this.conditions(key).map(c => ({...c, operator})), - } - const filterString: string = this.buildFilterString(clause) - this.updateFilterString(filterString) - } - - public updateFilterString = (newFilterString: string): void => { - const { - func: {id}, - bodyID, - declarationID, - } = this.props - - this.props.onChangeArg({ - funcID: id, - key: 'fn', - value: newFilterString, - declarationID, - bodyID, - generate: true, - }) - } - - public render() { - const { - db, - source, - tags, - filter, - bodyID, - declarationID, - onChangeArg, - onGenerateScript, - func: {id: funcID, args}, - } = this.props - const {value, key: argKey} = args[0] - - if (!this.clauseIsParseable) { - return ( - <> -

- Unable to render expression as a Builder -

- - - ) - } - - if (tags.length) { - return ( - - {tags.map(t => ( - - ))} - - ) - } - - return ( -
-
-
No tag keys found.
-
-
- ) - } - - public reduceNodesToClause( - nodes, - conditions: FilterTagCondition[] - ): ParsedClause { - if (!nodes.length) { - return this.constructClause(conditions) - } else if (this.noConditions(nodes, conditions)) { - return [{}, true] - } else if ( - ['OpenParen', 'CloseParen', 'Operator'].includes(nodes[0].type) - ) { - return this.skipNode(nodes, conditions) - } else if (this.conditionExtractable(nodes)) { - return this.extractCondition(nodes, conditions) - } else { - // Unparseable - return [{}, false] - } - } - - private constructClause(conditions: FilterTagCondition[]): ParsedClause { - const clause = _.groupBy(conditions, condition => condition.key) - if (this.validateClause(clause)) { - return [clause, true] - } else { - return [{}, false] - } - } - - private validateClause(clause) { - return Object.values(clause).every((conditions: FilterTagCondition[]) => - conditions.every(c => conditions[0].operator === c.operator) - ) - } - - private noConditions(nodes, conditions) { - return ( - !conditions.length && - nodes.length === 1 && - nodes[0].type === 'BooleanLiteral' && - nodes[0].source === 'true' - ) - } - - private skipNode([, ...nodes], conditions) { - return this.reduceNodesToClause(nodes, conditions) - } - - private conditionExtractable(nodes): boolean { - return ( - nodes.length >= 3 && - nodes[0].type === 'MemberExpression' && - nodes[1].type === 'Operator' && - this.supportedOperator(nodes[1].source) && - nodes[2].type === 'StringLiteral' - ) - } - - private supportedOperator(operator): boolean { - return operator === '==' || operator === '!=' - } - - private extractCondition( - [keyNode, operatorNode, valueNode, ...nodes], - conditions - ) { - const condition: FilterTagCondition = { - key: keyNode.property.name, - operator: operatorNode.source, - value: valueNode.source.replace(/"/g, ''), - } - return this.reduceNodesToClause(nodes, [...conditions, condition]) - } - - private handleClick(e: MouseEvent) { - e.stopPropagation() - } -} diff --git a/chronograf/ui/src/flux/components/FilterTagListItem.tsx b/chronograf/ui/src/flux/components/FilterTagListItem.tsx deleted file mode 100644 index d019c78842..0000000000 --- a/chronograf/ui/src/flux/components/FilterTagListItem.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import React, { - PureComponent, - CSSProperties, - ChangeEvent, - MouseEvent, -} from 'react' - -import _ from 'lodash' - -import {tagValues as fetchTagValues} from 'src/shared/apis/flux/metaQueries' -import {explorer} from 'src/flux/constants' -import { - SetFilterTagValue, - SetEquality, - FilterTagCondition, -} from 'src/types/flux' -import parseValuesColumn from 'src/shared/parsing/flux/values' -import FilterTagValueList from 'src/flux/components/FilterTagValueList' -import LoaderSkeleton from 'src/flux/components/LoaderSkeleton' -import LoadingSpinner from 'src/flux/components/LoadingSpinner' - -import {SchemaFilter, RemoteDataState} from 'src/types' -import {Source} from 'src/types/v2' - -interface Props { - tagKey: string - onSetEquality: SetEquality - onChangeValue: SetFilterTagValue - conditions: FilterTagCondition[] - operator: string - db: string - source: Source - filter: SchemaFilter[] -} - -interface State { - isOpen: boolean - loadingAll: RemoteDataState - loadingSearch: RemoteDataState - loadingMore: RemoteDataState - tagValues: string[] - searchTerm: string - limit: number - count: number | null -} - -export default class FilterTagListItem extends PureComponent { - constructor(props) { - super(props) - - this.state = { - isOpen: false, - loadingAll: RemoteDataState.NotStarted, - loadingSearch: RemoteDataState.NotStarted, - loadingMore: RemoteDataState.NotStarted, - tagValues: [], - count: null, - searchTerm: '', - limit: explorer.TAG_VALUES_LIMIT, - } - - this.debouncedOnSearch = _.debounce(() => { - this.searchTagValues() - this.getCount() - }, 250) - } - - public renderEqualitySwitcher() { - const {operator} = this.props - - if (!this.state.isOpen) { - return null - } - - return ( -
    -
  • - = -
  • -
  • - != -
  • -
- ) - } - - public render() { - const {tagKey, db, filter} = this.props - const {tagValues, searchTerm, loadingMore, count, limit} = this.state - const selectedValues = this.props.conditions.map(c => c.value) - - return ( -
-
-
-
- {tagKey} - Tag Key -
- {this.renderEqualitySwitcher()} -
- {this.state.isOpen && ( -
-
-
- - {this.isSearching && ( - - )} -
- - {!!count && ( -
{`${count} Tag Values`}
- )} -
- {this.isLoading && } - {!this.isLoading && ( - - )} -
- )} -
- ) - } - - private setEquality(equal: boolean) { - return (e): void => { - e.stopPropagation() - - const {tagKey} = this.props - this.props.onSetEquality(tagKey, equal) - } - } - - private get spinnerStyle(): CSSProperties { - return { - position: 'absolute', - right: '15px', - top: '6px', - } - } - - private get isSearching(): boolean { - return this.state.loadingSearch === RemoteDataState.Loading - } - - private get isLoading(): boolean { - return this.state.loadingAll === RemoteDataState.Loading - } - - private onSearch = (e: ChangeEvent): void => { - const searchTerm = e.target.value - - this.setState({searchTerm, loadingSearch: RemoteDataState.Loading}, () => - this.debouncedOnSearch() - ) - } - - private debouncedOnSearch() {} // See constructor - - private handleInputClick = (e: MouseEvent): void => { - e.stopPropagation() - } - - private searchTagValues = async () => { - try { - const tagValues = await this.getTagValues() - - this.setState({ - tagValues, - loadingSearch: RemoteDataState.Done, - }) - } catch (error) { - console.error(error) - this.setState({loadingSearch: RemoteDataState.Error}) - } - } - - private getAllTagValues = async () => { - this.setState({loadingAll: RemoteDataState.Loading}) - - try { - const tagValues = await this.getTagValues() - - this.setState({ - tagValues, - loadingAll: RemoteDataState.Done, - }) - } catch (error) { - console.error(error) - this.setState({loadingAll: RemoteDataState.Error}) - } - } - - private getMoreTagValues = async () => { - this.setState({loadingMore: RemoteDataState.Loading}) - - try { - const tagValues = await this.getTagValues() - - this.setState({ - tagValues, - loadingMore: RemoteDataState.Done, - }) - } catch (error) { - console.error(error) - this.setState({loadingMore: RemoteDataState.Error}) - } - } - - private getTagValues = async () => { - const {db, source, tagKey, filter} = this.props - const {searchTerm, limit} = this.state - const response = await fetchTagValues({ - source, - db, - filter, - tagKey, - limit, - searchTerm, - }) - - return parseValuesColumn(response) - } - - private handleClick = (e: MouseEvent) => { - e.stopPropagation() - - if (this.isFetchable) { - this.getCount() - this.getAllTagValues() - } - - this.setState({isOpen: !this.state.isOpen}) - } - - private handleLoadMoreValues = (): void => { - const {limit} = this.state - - this.setState( - {limit: limit + explorer.TAG_VALUES_LIMIT}, - this.getMoreTagValues - ) - } - - private async getCount() { - const {source, db, filter, tagKey} = this.props - const {limit, searchTerm} = this.state - try { - const response = await fetchTagValues({ - source, - db, - filter, - tagKey, - limit, - searchTerm, - count: true, - }) - - const parsed = parseValuesColumn(response) - - if (parsed.length !== 1) { - // We expect to never reach this state; instead, the Flux server should - // return a non-200 status code is handled earlier (after fetching). - // This return guards against some unexpected behavior---the Flux server - // returning a 200 status code but ALSO having an error in the CSV - // response body - return - } - - const count = Number(parsed[0]) - - this.setState({count}) - } catch (error) { - console.error(error) - } - } - - private get loadMoreCount(): number { - const {count, limit} = this.state - - return Math.min(Math.abs(count - limit), explorer.TAG_VALUES_LIMIT) - } - - private get isFetchable(): boolean { - const {isOpen, loadingAll} = this.state - - return ( - !isOpen && - (loadingAll === RemoteDataState.NotStarted || - loadingAll !== RemoteDataState.Error) - ) - } - - private get className(): string { - const {isOpen} = this.state - const openClass = isOpen ? 'expanded' : '' - - return `flux-schema-tree ${openClass}` - } -} diff --git a/chronograf/ui/src/flux/components/FilterTagValueList.tsx b/chronograf/ui/src/flux/components/FilterTagValueList.tsx deleted file mode 100644 index 6f5c82325f..0000000000 --- a/chronograf/ui/src/flux/components/FilterTagValueList.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, {PureComponent, MouseEvent} from 'react' -import _ from 'lodash' - -import FilterTagValueListItem from 'src/flux/components/FilterTagValueListItem' -import LoadingSpinner from 'src/flux/components/LoadingSpinner' -import {SchemaFilter} from 'src/types' -import {SetFilterTagValue} from 'src/types/flux' - -interface Props { - db: string - tagKey: string - values: string[] - selectedValues: string[] - onChangeValue: SetFilterTagValue - filter: SchemaFilter[] - isLoadingMoreValues: boolean - onLoadMoreValues: () => void - shouldShowMoreValues: boolean - loadMoreCount: number -} - -export default class FilterTagValueList extends PureComponent { - public render() { - const {values, tagKey, shouldShowMoreValues} = this.props - - return ( - <> - {values.map((v, i) => ( - - ))} - {shouldShowMoreValues && ( -
-
- -
-
- )} - - ) - } - - private handleClick = (e: MouseEvent) => { - e.stopPropagation() - this.props.onLoadMoreValues() - } - - private get buttonValue(): string | JSX.Element { - const {isLoadingMoreValues, loadMoreCount, tagKey} = this.props - - if (isLoadingMoreValues) { - return - } - - return `Load next ${loadMoreCount} values for ${tagKey}` - } -} diff --git a/chronograf/ui/src/flux/components/FilterTagValueListItem.tsx b/chronograf/ui/src/flux/components/FilterTagValueListItem.tsx deleted file mode 100644 index 3d0cb4c8ec..0000000000 --- a/chronograf/ui/src/flux/components/FilterTagValueListItem.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, {PureComponent, MouseEvent} from 'react' - -import {SetFilterTagValue} from 'src/types/flux' - -interface Props { - tagKey: string - value: string - onChangeValue: SetFilterTagValue - selected: boolean -} - -class FilterTagValueListItem extends PureComponent { - constructor(props) { - super(props) - } - - public render() { - const {value} = this.props - - return ( -
-
-
-
- {value} - Tag Value -
-
-
- ) - } - - private handleClick = (e: MouseEvent) => { - const {tagKey, value, selected} = this.props - - e.stopPropagation() - this.props.onChangeValue(tagKey, value, !selected) - } - - private get listItemClasses() { - const baseClasses = 'flux-schema--item query-builder--list-item' - return this.props.selected ? baseClasses + ' active' : baseClasses - } -} - -export default FilterTagValueListItem diff --git a/chronograf/ui/src/flux/components/FluxEdit.tsx b/chronograf/ui/src/flux/components/FluxEdit.tsx deleted file mode 100644 index 21ea93bbf7..0000000000 --- a/chronograf/ui/src/flux/components/FluxEdit.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, {PureComponent, ChangeEvent, FormEvent} from 'react' -import _ from 'lodash' - -import FluxForm from 'src/flux/components/FluxForm' - -import {Service, Notification} from 'src/types' -import { - fluxUpdated, - fluxNotUpdated, - fluxNameAlreadyTaken, -} from 'src/shared/copy/notifications' -import {UpdateServiceAsync} from 'src/shared/actions/services' -import {FluxFormMode} from 'src/flux/constants/connection' - -interface Props { - service: Service - services: Service[] - onDismiss?: () => void - updateService: UpdateServiceAsync - notify: (message: Notification) => void -} - -interface State { - service: Service -} - -class FluxEdit extends PureComponent { - public static getDerivedStateFromProps(nextProps: Props, prevState: State) { - if (_.isEmpty(prevState.service) && !_.isEmpty(nextProps.service)) { - return {service: nextProps.service} - } - return null - } - - constructor(props: Props) { - super(props) - this.state = { - service: this.props.service, - } - } - - public render() { - return ( - - ) - } - - private handleInputChange = (e: ChangeEvent): void => { - const {value, name} = e.target - const update = {[name]: value} - - this.setState({service: {...this.state.service, ...update}}) - } - - private handleSubmit = async ( - e: FormEvent - ): Promise => { - e.preventDefault() - const {notify, onDismiss, updateService, services} = this.props - const {service} = this.state - service.name = service.name.trim() - let isNameTaken = false - services.forEach(s => { - if (s.name === service.name && s.id !== service.id) { - isNameTaken = true - } - }) - - if (isNameTaken) { - notify(fluxNameAlreadyTaken(service.name)) - return - } - - try { - await updateService(service) - } catch (error) { - notify(fluxNotUpdated(error.message)) - return - } - - notify(fluxUpdated) - if (onDismiss) { - onDismiss() - } - } -} - -export default FluxEdit diff --git a/chronograf/ui/src/flux/components/FluxForm.tsx b/chronograf/ui/src/flux/components/FluxForm.tsx deleted file mode 100644 index d99ea5e39c..0000000000 --- a/chronograf/ui/src/flux/components/FluxForm.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, {ChangeEvent, PureComponent} from 'react' -import _ from 'lodash' - -import Input from 'src/shared/components/KapacitorFormInput' - -import {NewService} from 'src/types' -import {FluxFormMode} from 'src/flux/constants/connection' - -interface Props { - service: NewService - mode: FluxFormMode - onSubmit: (e: ChangeEvent) => void - onInputChange: (e: ChangeEvent) => void -} - -class FluxForm extends PureComponent { - public render() { - const {service, onSubmit, onInputChange} = this.props - const name = _.get(service, 'name', '') - - return ( -
- - -
- {this.saveButton} -
-
- ) - } - - private get saveButton(): JSX.Element { - const {mode} = this.props - - let text = 'Connect' - - if (mode === FluxFormMode.EDIT) { - text = 'Save Changes' - } - - return ( - - ) - } - - private get url(): string { - const {service} = this.props - return _.get(service, 'url', '') - } -} - -export default FluxForm diff --git a/chronograf/ui/src/flux/components/FluxGraph.tsx b/chronograf/ui/src/flux/components/FluxGraph.tsx deleted file mode 100644 index 35ac3be939..0000000000 --- a/chronograf/ui/src/flux/components/FluxGraph.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// Libraries -import React, {PureComponent} from 'react' -import {connect} from 'react-redux' - -// Components -import Dygraph from 'src/shared/components/dygraph/Dygraph' - -// Utils -import {fluxTablesToDygraph} from 'src/shared/parsing/flux/dygraph' - -// Actions -import {setHoverTime as setHoverTimeAction} from 'src/dashboards/actions/v2/hoverTime' - -// Constants -import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' - -// Types -import {FluxTable} from 'src/types' -import {ViewType} from 'src/types/v2/dashboards' - -interface Props { - data: FluxTable[] - setHoverTime: (time: string) => void -} - -class FluxGraph extends PureComponent { - public render() { - const {dygraphsData, labels} = fluxTablesToDygraph(this.props.data) - - return ( -
- -
- ) - } - - private get options() { - return { - axisLineColor: '#383846', - gridLineColor: '#383846', - } - } -} - -const mdtp = { - setHoverTime: setHoverTimeAction, -} - -export default connect(null, mdtp)(FluxGraph) diff --git a/chronograf/ui/src/flux/components/FluxNew.tsx b/chronograf/ui/src/flux/components/FluxNew.tsx deleted file mode 100644 index 931b76f84e..0000000000 --- a/chronograf/ui/src/flux/components/FluxNew.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, {PureComponent, ChangeEvent, FormEvent} from 'react' - -import FluxForm from 'src/flux/components/FluxForm' - -import {NewService, Source, Service, Notification} from 'src/types' -import { - fluxCreated, - fluxNotCreated, - fluxNameAlreadyTaken, -} from 'src/shared/copy/notifications' -import { - CreateServiceAsync, - SetActiveServiceAsync, -} from 'src/shared/actions/services' -import {FluxFormMode} from 'src/flux/constants/connection' -import {getDeep} from 'src/utils/wrappers' - -interface Props { - source: Source - services: Service[] - setActiveFlux?: SetActiveServiceAsync - onDismiss?: () => void - createService: CreateServiceAsync - router?: {push: (url: string) => void} - notify: (message: Notification) => void -} - -interface State { - service: NewService -} - -const port = 8093 - -class FluxNew extends PureComponent { - constructor(props) { - super(props) - this.state = { - service: this.defaultService, - } - } - - public render() { - return ( - - ) - } - - private handleInputChange = (e: ChangeEvent): void => { - const {value, name} = e.target - const update = {[name]: value} - - this.setState({service: {...this.state.service, ...update}}) - } - - private handleSubmit = async ( - e: FormEvent - ): Promise => { - e.preventDefault() - const { - notify, - router, - source, - services, - onDismiss, - setActiveFlux, - createService, - } = this.props - const {service} = this.state - service.name = service.name.trim() - const isNameTaken = services.some(s => s.name === service.name) - - if (isNameTaken) { - notify(fluxNameAlreadyTaken(service.name)) - return - } - - try { - const active = this.activeService - const s = await createService(source, service) - if (setActiveFlux) { - await setActiveFlux(source, s, active) - } - if (router) { - router.push(`/sources/${source.id}/flux/${s.id}/edit`) - } - } catch (error) { - notify(fluxNotCreated(error.message)) - return - } - - notify(fluxCreated) - if (onDismiss) { - onDismiss() - } - } - - private get defaultService(): NewService { - return { - name: 'Flux', - url: this.url, - username: '', - insecureSkipVerify: false, - type: 'flux', - metadata: { - active: true, - }, - } - } - - private get activeService(): Service { - const {services} = this.props - const activeService = services.find(s => { - return getDeep(s, 'metadata.active', false) - }) - return activeService || services[0] - } - - private get url(): string { - const parser = document.createElement('a') - parser.href = this.props.source.url - - return `${parser.protocol}//${parser.hostname}:${port}` - } -} - -export default FluxNew diff --git a/chronograf/ui/src/flux/components/FromDatabaseDropdown.test.tsx b/chronograf/ui/src/flux/components/FromDatabaseDropdown.test.tsx deleted file mode 100644 index f5750ae719..0000000000 --- a/chronograf/ui/src/flux/components/FromDatabaseDropdown.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import {shallow} from 'enzyme' -import FromDatabaseDropdown from 'src/flux/components/FromDatabaseDropdown' -import {source} from 'src/sources/resources' - -jest.mock('src/shared/apis/metaQuery', () => require('mocks/flux/apis')) - -const setup = () => { - const props = { - funcID: '1', - argKey: 'db', - value: 'db1', - bodyID: '2', - declarationID: '1', - source, - onChangeArg: () => {}, - } - - const wrapper = shallow() - - return { - wrapper, - } -} - -describe('Flux.Components.FromDatabaseDropdown', () => { - describe('rendering', () => { - it('renders without errors', () => { - const {wrapper} = setup() - expect(wrapper.exists()).toBe(true) - }) - }) -}) diff --git a/chronograf/ui/src/flux/components/FromDatabaseDropdown.tsx b/chronograf/ui/src/flux/components/FromDatabaseDropdown.tsx deleted file mode 100644 index 9e66959f26..0000000000 --- a/chronograf/ui/src/flux/components/FromDatabaseDropdown.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, {PureComponent} from 'react' - -import {showDatabases} from 'src/shared/apis/metaQuery' -import showDatabasesParser from 'src/shared/parsing/showDatabases' - -import Dropdown from 'src/shared/components/Dropdown' -import {OnChangeArg} from 'src/types/flux' - -import {Source} from 'src/types/v2' - -interface Props { - funcID: string - argKey: string - value: string - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - source: Source -} - -interface State { - dbs: string[] -} - -interface DropdownItem { - text: string -} - -class FromDatabaseDropdown extends PureComponent { - constructor(props) { - super(props) - this.state = { - dbs: [], - } - } - - public async componentDidMount() { - const {source} = this.props - - try { - // (watts): TODO: hit actual buckets API - const {data} = await showDatabases(source.links.buckets) - const {databases} = showDatabasesParser(data) - const sorted = databases.sort() - - this.setState({dbs: sorted}) - } catch (err) { - console.error(err) - } - } - - public render() { - const {value, argKey} = this.props - - return ( -
- - -
- ) - } - - private handleChooseDatabase = (item: DropdownItem): void => { - const {argKey, funcID, onChangeArg, bodyID, declarationID} = this.props - onChangeArg({ - funcID, - key: argKey, - value: item.text, - bodyID, - declarationID, - generate: true, - }) - } - - private get items(): DropdownItem[] { - return this.state.dbs.map(text => ({text})) - } -} - -export default FromDatabaseDropdown diff --git a/chronograf/ui/src/flux/components/FuncArg.test.tsx b/chronograf/ui/src/flux/components/FuncArg.test.tsx deleted file mode 100644 index 0e7e5daf66..0000000000 --- a/chronograf/ui/src/flux/components/FuncArg.test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import {shallow} from 'enzyme' -import FuncArg from 'src/flux/components/FuncArg' -import {source} from 'src/sources/resources' - -const setup = () => { - const props = { - funcID: '', - bodyID: '', - funcName: '', - declarationID: '', - argKey: '', - args: [], - value: '', - type: '', - source, - onChangeArg: () => {}, - onGenerateScript: () => {}, - } - - const wrapper = shallow() - - return { - wrapper, - } -} - -describe('Flux.Components.FuncArg', () => { - describe('rendering', () => { - it('renders without errors', () => { - const {wrapper} = setup() - - expect(wrapper.exists()).toBe(true) - }) - }) -}) diff --git a/chronograf/ui/src/flux/components/FuncArg.tsx b/chronograf/ui/src/flux/components/FuncArg.tsx deleted file mode 100644 index 9afe9ab066..0000000000 --- a/chronograf/ui/src/flux/components/FuncArg.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, {PureComponent} from 'react' -import _ from 'lodash' - -import FuncArgInput from 'src/flux/components/FuncArgInput' -import FuncArgTextArea from 'src/flux/components/FuncArgTextArea' -import FuncArgBool from 'src/flux/components/FuncArgBool' -import {ErrorHandling} from 'src/shared/decorators/errors' -import FromDatabaseDropdown from 'src/flux/components/FromDatabaseDropdown' - -import {funcNames, argTypes} from 'src/flux/constants' -import {OnChangeArg, Arg, OnGenerateScript} from 'src/types/flux' - -import {Source} from 'src/types/v2' - -interface Props { - source: Source - funcName: string - funcID: string - argKey: string - args: Arg[] - value: string | boolean | {[x: string]: string} - type: string - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - onGenerateScript: OnGenerateScript -} - -@ErrorHandling -class FuncArg extends PureComponent { - public render() { - const { - argKey, - value, - type, - bodyID, - funcID, - source, - funcName, - onChangeArg, - declarationID, - onGenerateScript, - } = this.props - - if (funcName === funcNames.FROM) { - return ( - - ) - } - - switch (type) { - case argTypes.STRING: - case argTypes.DURATION: - case argTypes.TIME: - case argTypes.REGEXP: - case argTypes.FLOAT: - case argTypes.INT: - case argTypes.UINT: - case argTypes.INVALID: - case argTypes.ARRAY: { - return ( - - ) - } - - case argTypes.BOOL: { - return ( - - ) - } - case argTypes.FUNCTION: { - return ( - - ) - } - case argTypes.NIL: { - // TODO: handle nil type - return ( -
- -
{value}
-
- ) - } - default: { - return ( -
- -
{value}
-
- ) - } - } - } - - private get value(): string { - return this.props.value.toString() - } - - private get boolValue(): boolean { - return this.props.value === true - } - - private get isFirstArg(): boolean { - const {args, argKey} = this.props - - const firstArg = _.first(args) - - return firstArg.key === argKey - } -} - -export default FuncArg diff --git a/chronograf/ui/src/flux/components/FuncArgBool.tsx b/chronograf/ui/src/flux/components/FuncArgBool.tsx deleted file mode 100644 index 9096d7293e..0000000000 --- a/chronograf/ui/src/flux/components/FuncArgBool.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, {PureComponent} from 'react' -import SlideToggle from 'src/clockface/components/slide_toggle/SlideToggle' -import {ComponentColor} from 'src/clockface/types' - -import {OnChangeArg} from 'src/types/flux' - -interface Props { - argKey: string - value: boolean - funcID: string - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - onGenerateScript: () => void -} - -class FuncArgBool extends PureComponent { - public render() { - return ( -
- -
- -
-
- ) - } - - private handleToggleClick = (): void => { - const { - argKey, - funcID, - bodyID, - onChangeArg, - declarationID, - value, - } = this.props - onChangeArg({ - key: argKey, - value: !value, - funcID, - bodyID, - declarationID, - generate: true, - }) - } -} - -export default FuncArgBool diff --git a/chronograf/ui/src/flux/components/FuncArgInput.test.tsx b/chronograf/ui/src/flux/components/FuncArgInput.test.tsx deleted file mode 100644 index 052b21c076..0000000000 --- a/chronograf/ui/src/flux/components/FuncArgInput.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import {shallow} from 'enzyme' -import FuncArgInput from 'src/flux/components/FuncArgInput' - -const setup = (override?) => { - const props = { - funcID: '1', - argKey: 'db', - value: 'db1', - type: 'string', - onChangeArg: () => {}, - onGenerateScript: () => {}, - ...override, - } - - const wrapper = shallow() - - return { - wrapper, - props, - } -} - -describe('Flux.Components.FuncArgInput', () => { - describe('rendering', () => { - it('renders without errors', () => { - const {wrapper} = setup() - - expect(wrapper.exists()).toBe(true) - }) - }) - - describe('user interraction', () => { - describe('typing', () => { - describe('hitting enter', () => { - it('generates a new script when Enter is pressed', () => { - const onGenerateScript = jest.fn() - const preventDefault = jest.fn() - - const {wrapper} = setup({onGenerateScript}) - - const input = wrapper.find('input') - input.simulate('keydown', {key: 'Enter', preventDefault}) - - expect(onGenerateScript).toHaveBeenCalledTimes(1) - expect(preventDefault).toHaveBeenCalledTimes(1) - }) - - it('it does not generate a new script when typing', () => { - const onGenerateScript = jest.fn() - const preventDefault = jest.fn() - - const {wrapper} = setup({onGenerateScript}) - - const input = wrapper.find('input') - input.simulate('keydown', {key: 'a', preventDefault}) - - expect(onGenerateScript).not.toHaveBeenCalled() - expect(preventDefault).not.toHaveBeenCalled() - }) - }) - - describe('changing the input value', () => { - it('calls onChangeArg', () => { - const onChangeArg = jest.fn() - const {wrapper, props} = setup({onChangeArg}) - - const input = wrapper.find('input') - const value = 'db2' - input.simulate('change', {target: {value}}) - const {funcID, argKey} = props - - expect(onChangeArg).toHaveBeenCalledWith({funcID, key: argKey, value}) - }) - }) - }) - }) -}) diff --git a/chronograf/ui/src/flux/components/FuncArgInput.tsx b/chronograf/ui/src/flux/components/FuncArgInput.tsx deleted file mode 100644 index e649e7c2e0..0000000000 --- a/chronograf/ui/src/flux/components/FuncArgInput.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react' -import {ErrorHandling} from 'src/shared/decorators/errors' -import {OnChangeArg, OnGenerateScript} from 'src/types/flux' - -interface Props { - funcID: string - argKey: string - value: string - type: string - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - onGenerateScript: OnGenerateScript - autoFocus?: boolean -} - -@ErrorHandling -class FuncArgInput extends PureComponent { - public render() { - const {argKey, value, type, autoFocus} = this.props - - return ( -
- -
- -
-
- ) - } - - private handleKeyDown = (e: KeyboardEvent) => { - if (e.key !== 'Enter') { - return - } - - e.preventDefault() - this.props.onGenerateScript() - } - - private handleChange = (e: ChangeEvent) => { - const {funcID, argKey, bodyID, declarationID} = this.props - - this.props.onChangeArg({ - funcID, - key: argKey, - value: e.target.value, - declarationID, - bodyID, - }) - } -} - -export default FuncArgInput diff --git a/chronograf/ui/src/flux/components/FuncArgTextArea.tsx b/chronograf/ui/src/flux/components/FuncArgTextArea.tsx deleted file mode 100644 index fbb31b0907..0000000000 --- a/chronograf/ui/src/flux/components/FuncArgTextArea.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react' -import {ErrorHandling} from 'src/shared/decorators/errors' -import {OnChangeArg} from 'src/types/flux' - -interface Props { - funcID: string - argKey: string - value: string - type: string - bodyID: string - declarationID: string - onChangeArg: OnChangeArg - onGenerateScript: () => void - inputType?: string -} - -interface State { - height: string -} - -@ErrorHandling -class FuncArgTextArea extends PureComponent { - private ref: React.RefObject - - constructor(props: Props) { - super(props) - this.ref = React.createRef() - this.state = { - height: '100px', - } - } - - public componentDidMount() { - this.setState({height: this.height}) - } - - public render() { - const {argKey, value, type} = this.props - - return ( -
- -
-