diff --git a/ui/package.json b/ui/package.json index ed254c8f9e..36b6901503 100644 --- a/ui/package.json +++ b/ui/package.json @@ -168,6 +168,7 @@ "react-dnd-html5-backend": "^2.6.0", "react-dom": "^16.8.2", "react-grid-layout": "^0.16.6", + "react-loadable": "^5.5.0", "react-markdown": "^4.0.3", "react-monaco-editor": "^0.32.1", "react-redux": "^5.1.2", diff --git a/ui/src/clockface/components/wizard/WizardOverlay.tsx b/ui/src/clockface/components/wizard/WizardOverlay.tsx index 2981742ebc..f5ecf58e30 100644 --- a/ui/src/clockface/components/wizard/WizardOverlay.tsx +++ b/ui/src/clockface/components/wizard/WizardOverlay.tsx @@ -7,7 +7,8 @@ import {Overlay} from '@influxdata/clockface' import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { - children: any + children: string | React.ReactNode + footer?: string | React.ReactNode title: string maxWidth: number onDismiss: () => void @@ -20,7 +21,7 @@ class WizardOverlay extends PureComponent { } public render() { - const {title, maxWidth, children, onDismiss} = this.props + const {title, maxWidth, children, footer, onDismiss} = this.props return ( @@ -29,6 +30,7 @@ class WizardOverlay extends PureComponent {
{children}
+ {footer && {footer}}
) diff --git a/ui/src/dataLoaders/actions/telegrafEditor.ts b/ui/src/dataLoaders/actions/telegrafEditor.ts index 60a2e94d57..49d61133ef 100644 --- a/ui/src/dataLoaders/actions/telegrafEditor.ts +++ b/ui/src/dataLoaders/actions/telegrafEditor.ts @@ -6,14 +6,14 @@ import { export type PluginAction = ReturnType -const setPlugins = (plugins: TelegrafEditorPluginState) => ({ +export const setPlugins = (plugins: TelegrafEditorPluginState) => ({ type: 'SET_TELEGRAF_EDITOR_PLUGINS' as 'SET_TELEGRAF_EDITOR_PLUGINS', payload: plugins, }) export type ActivePluginAction = ReturnType -const setActivePlugins = (plugins: TelegrafEditorActivePluginState) => ({ +export const setActivePlugins = (plugins: TelegrafEditorActivePluginState) => ({ type: 'SET_TELEGRAF_EDITOR_ACTIVE_PLUGINS' as 'SET_TELEGRAF_EDITOR_ACTIVE_PLUGINS', payload: plugins, }) diff --git a/ui/src/dataLoaders/components/TelegrafEditor.scss b/ui/src/dataLoaders/components/TelegrafEditor.scss new file mode 100644 index 0000000000..a3ea7bccfa --- /dev/null +++ b/ui/src/dataLoaders/components/TelegrafEditor.scss @@ -0,0 +1,31 @@ +@import '~src/style/_influx-colors.scss'; + +.telegraf-editor--plugins { + .display { + color: $g7-graphite; + margin-top: 8px; + padding: 0 12px; + } + + .entry { + cursor: pointer; + padding: 0 12px; + padding-left: 24px; + + label { + font-size: 12px; + color: $g9-mountain; + display: block; + margin-bottom: 4px; + cursor: pointer; + } + } + + .input, + .output, + .processor, + .aggregator, + .bundle { + @extend .entry; + } +} diff --git a/ui/src/dataLoaders/components/TelegrafEditor.tsx b/ui/src/dataLoaders/components/TelegrafEditor.tsx new file mode 100644 index 0000000000..183df87958 --- /dev/null +++ b/ui/src/dataLoaders/components/TelegrafEditor.tsx @@ -0,0 +1,125 @@ +// Libraries +import React, {PureComponent} from 'react' +import {connect} from 'react-redux' + +// Components +import {Grid, Columns} from '@influxdata/clockface' +import TelegrafEditorSidebar from 'src/dataLoaders/components/TelegrafEditorSidebar' +import TelegrafEditorMonaco from 'src/dataLoaders/components/TelegrafEditorMonaco' + +// Styles +import './TelegrafEditor.scss' + +// Utils +import {ErrorHandling} from 'src/shared/decorators/errors' + +// Types +import {AppState} from 'src/types' +import { + TelegrafEditorActivePlugin, + TelegrafEditorPlugin, + TelegrafEditorBasicPlugin, +} from 'src/dataLoaders/reducers/telegrafEditor' + +type AllPlugin = TelegrafEditorPlugin | TelegrafEditorActivePlugin + +interface StateProps { + pluginHashMap: {[k: string]: AllPlugin} +} + +type Props = StateProps + +@ErrorHandling +class TelegrafEditor extends PureComponent { + _editor: any = null + + render() { + return ( + + +

What do you want to monitor?

+
+ Telegraf is a plugin-based data collection agent which writes + metrics to a bucket in InfluxDB +
+ Use the editor below to configure as many of the 200+{' '} + + plugins + {' '} + as you require +
+
+ + + + + + +
+ ) + } + + private connect = (elem: any) => { + this._editor = elem + } + + private handleJump = (which: TelegrafEditorActivePlugin) => { + this._editor.getWrappedInstance().jump(which.line) + } + + private handleAdd = (which: TelegrafEditorPlugin) => { + const editor = this._editor.getWrappedInstance() + const line = editor.nextLine() + + if (which.type === 'bundle') { + which.include + .filter( + item => + this.props.pluginHashMap[item] && + this.props.pluginHashMap[item].type !== 'bundle' + ) + .map( + item => + ( + (this.props.pluginHashMap[item] as TelegrafEditorBasicPlugin) || + {} + ).code + ) + .filter(i => !!i) + .reverse() + .forEach(item => { + editor.insert(item, line) + }) + } else { + editor.insert(which.code || '', line) + } + + editor.jump(line) + } +} + +const mstp = (state: AppState): StateProps => { + const pluginHashMap = state.telegrafEditorPlugins + .filter( + (a: TelegrafEditorPlugin) => a.type !== 'bundle' || !!a.include.length + ) + .reduce((prev, curr) => { + prev[curr.name] = curr + return prev + }, {}) + + return { + pluginHashMap, + } +} + +export default connect( + mstp, + null +)(TelegrafEditor) diff --git a/ui/src/dataLoaders/components/TelegrafEditorMonaco.tsx b/ui/src/dataLoaders/components/TelegrafEditorMonaco.tsx new file mode 100644 index 0000000000..360b7e713c --- /dev/null +++ b/ui/src/dataLoaders/components/TelegrafEditorMonaco.tsx @@ -0,0 +1,150 @@ +import React, {PureComponent} from 'react' +import {connect} from 'react-redux' +import {AppState} from 'src/types' +import Editor from 'src/shared/components/TomlMonacoEditor' +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' +import {setText, setActivePlugins} from 'src/dataLoaders/actions/telegrafEditor' +import {TelegrafEditorPluginType} from 'src/dataLoaders/reducers/telegrafEditor' + +const PLUGIN_REGEX = /\[\[\s*(inputs|outputs|processors|aggregators)\.(.+)\s*\]\]/ + +interface DispatchProps { + onSetText: typeof setText + onSetActivePlugins: typeof setActivePlugins +} + +interface StateProps { + script: string +} + +type Props = StateProps & DispatchProps + +interface InterumMatchFormat { + name: string + type: TelegrafEditorPluginType + line: number +} + +class TelegrafEditorMonaco extends PureComponent { + _editor: monacoEditor.editor.IStandaloneCodeEditor = null + + render() { + const {script} = this.props + + return ( + + ) + } + + private extractPluginList() { + if (!this._editor) { + return + } + + const matches: Array< + monacoEditor.editor.FindMatch + > = this._editor + .getModel() + .findMatches(PLUGIN_REGEX as any, false, true, false, null, true) + + const plugins = matches.map( + (m: monacoEditor.editor.FindMatch): InterumMatchFormat => { + return { + type: m.matches[1].slice(0, -1) as TelegrafEditorPluginType, + name: m.matches[2], + line: m.range.startLineNumber, + } + } + ) + + this.props.onSetActivePlugins(plugins) + } + + private connect = (editor: monacoEditor.editor.IStandaloneCodeEditor) => { + this._editor = editor + this.extractPluginList() + } + + private handleChange = (evt: string) => { + this.extractPluginList() + this.props.onSetText(evt) + } + + public jump(line: number) { + this._editor.revealLineInCenter(line) + } + + public nextLine(): number { + const position = this._editor.getPosition() + const matches = this._editor + .getModel() + .findNextMatch(PLUGIN_REGEX as any, position, true, false, null, true) + let lineNumber + + if ( + position.lineNumber === 1 || + !matches || + position.lineNumber > matches.range.startLineNumber + ) { + //add it to the bottom + lineNumber = this._editor.getModel().getLineCount() + } else { + lineNumber = matches.range.startLineNumber - 1 + } + + return lineNumber + } + + public insert(text: string, line: number) { + this._editor.setPosition({column: 1, lineNumber: line}) + this._editor.executeEdits('', [ + { + range: { + startLineNumber: line, + startColumn: 1, + endLineNumber: line, + endColumn: 1, + } as monacoEditor.Range, + text: text, + forceMoveMarkers: true, + }, + ]) + } +} + +export {TelegrafEditorMonaco} + +const mstp = (state: AppState): StateProps => { + const map = state.telegrafEditorPlugins.reduce((prev, curr) => { + prev[curr.name] = curr + return prev + }, {}) + + const script = + state.telegrafEditor.text || + map['__default__'].include + .map((i: string) => { + return map[i].code + }) + .join('\n') + + return { + script, + } +} + +const mdtp: DispatchProps = { + onSetActivePlugins: setActivePlugins, + onSetText: setText, +} + +export default connect( + mstp, + mdtp, + null, + {withRef: true} +)(TelegrafEditorMonaco) diff --git a/ui/src/dataLoaders/components/TelegrafEditorPluginList.tsx b/ui/src/dataLoaders/components/TelegrafEditorPluginList.tsx new file mode 100644 index 0000000000..69d3cc2633 --- /dev/null +++ b/ui/src/dataLoaders/components/TelegrafEditorPluginList.tsx @@ -0,0 +1,108 @@ +import React, {PureComponent} from 'react' +import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar' +import { + TelegrafEditorPluginState, + TelegrafEditorActivePluginState, + TelegrafEditorActivePlugin, + TelegrafEditorPlugin, +} from 'src/dataLoaders/reducers/telegrafEditor' + +type ListPlugin = TelegrafEditorPlugin | TelegrafEditorActivePlugin + +interface InterimListFormat { + category: string + items: Array +} + +function groupPlugins(plugins: Array, pluginFilter: string) { + const map = plugins.reduce( + (prev: {[k: string]: Array}, curr: ListPlugin) => { + if (curr.name === '__default__') { + return prev + } + + if (!prev.hasOwnProperty(curr.type)) { + prev[curr.type] = [] + } + + prev[curr.type].push(curr) + + return prev + }, + {} + ) + + return ['bundle', 'input', 'output', 'processor', 'aggregator'] + .map( + (k: string): InterimListFormat => { + return { + category: k, + items: (map[k] || []).filter((a: ListPlugin) => + (a.name || '').includes(pluginFilter) + ), + } + } + ) + .filter((k: InterimListFormat) => k.items.length) + .reduce((prev, curr) => { + prev.push({ + type: 'display', + name: '-- ' + curr.category + ' --', + }) + + const items = curr.items.slice(0).sort((a: ListPlugin, b: ListPlugin) => { + return (a.name || '').localeCompare(b.name || '') + }) + + prev.push(...items) + + return prev + }, []) +} + +interface PluginProps { + plugins: TelegrafEditorPluginState | TelegrafEditorActivePluginState + filter: string + onClick: (which: ListPlugin) => void +} + +class PluginList extends PureComponent { + render() { + const {plugins, filter, onClick} = this.props + const list = groupPlugins(plugins, filter).map((k: ListPlugin) => { + if (k.type === 'display') { + return ( +
+ {k.name} +
+ ) + } + + let description + + // NOTE: written this way to bypass typescript: alex + if (k['description']) { + description = + } + + return ( +
onClick(k)} + > + {k.name} + {description} +
+ ) + }) + + return ( + + {list} + + ) + } +} + +export default PluginList diff --git a/ui/src/dataLoaders/components/TelegrafEditorSidebar.tsx b/ui/src/dataLoaders/components/TelegrafEditorSidebar.tsx new file mode 100644 index 0000000000..cf9425d87c --- /dev/null +++ b/ui/src/dataLoaders/components/TelegrafEditorSidebar.tsx @@ -0,0 +1,181 @@ +import React, {PureComponent, SyntheticEvent, ChangeEvent} from 'react' +import {connect} from 'react-redux' +import PluginList from 'src/dataLoaders/components/TelegrafEditorPluginList' +import BucketDropdown from 'src/dataLoaders/components/BucketsDropdown' +import {AppState, Bucket} from 'src/types' +import { + TelegrafEditorPluginState, + TelegrafEditorActivePluginState, + TelegrafEditorPlugin, + TelegrafEditorActivePlugin, +} from 'src/dataLoaders/reducers/telegrafEditor' +import { + setFilter, + setBucket, + setMode, +} from 'src/dataLoaders/actions/telegrafEditor' +import { + Input, + IconFont, + FormElement, + Grid, + Columns, + Tabs, + ComponentSize, + Orientation, +} from '@influxdata/clockface' + +interface PluginStateProps { + plugins: TelegrafEditorPluginState + filter: string +} + +interface ActivePluginStateProps { + plugins: TelegrafEditorActivePluginState + filter: string +} + +const mstp_1 = (state: AppState): ActivePluginStateProps => { + const plugins = state.telegrafEditorActivePlugins || [] + const filter = state.telegrafEditor.filter + + return { + plugins, + filter, + } +} + +const ActivePluginList = connect( + mstp_1, + null +)(PluginList) + +const mstp_2 = (state: AppState): PluginStateProps => { + const plugins = state.telegrafEditorPlugins || [] + const filter = state.telegrafEditor.filter + + return { + plugins, + filter, + } +} + +const AllPluginList = connect( + mstp_2, + null +)(PluginList) + +interface StateProps { + buckets: Bucket[] + bucket: Bucket + filter: string + mode: 'adding' | 'indexing' +} + +interface DispatchProps { + onSetFilter: typeof setFilter + onSetBucket: typeof setBucket + onSetMode: typeof setMode +} + +interface OwnProps { + onJump: (which: TelegrafEditorActivePlugin) => void + onAdd: (which: TelegrafEditorPlugin) => void +} + +type TelegrafEditorSidebarProps = StateProps & DispatchProps & OwnProps + +class TelegrafEditorSideBar extends PureComponent { + render() { + const { + bucket, + buckets, + filter, + mode, + onAdd, + onJump, + onSetMode, + onSetBucket, + onSetFilter, + } = this.props + return ( + + + + + ) => { + onSetFilter((evt.target as any).value) + }} + onChange={(evt: ChangeEvent) => { + onSetFilter(evt.target.value) + }} + placeholder="Filter Plugins..." + /> + + + { + onSetMode('indexing') + }} + /> + { + onSetMode('adding') + }} + /> + + + {mode === 'indexing' && } + {mode === 'adding' && } + + + + ) + } +} + +const mstp_3 = (state: AppState): StateProps => { + const filter = state.telegrafEditor.filter + const mode = state.telegrafEditor.mode + const buckets = state.buckets.list || [] + const bucket = + state.telegrafEditor.bucket || buckets.length + ? buckets[0] + : ({id: null} as Bucket) + + return { + buckets, + bucket, + mode, + filter, + } +} + +const mdtp_3: DispatchProps = { + onSetMode: setMode, + onSetBucket: setBucket, + onSetFilter: setFilter, +} + +export default connect( + mstp_3, + mdtp_3 +)(TelegrafEditorSideBar) diff --git a/ui/src/dataLoaders/components/collectorsWizard/CollectorsWizard.tsx b/ui/src/dataLoaders/components/collectorsWizard/CollectorsWizard.tsx index a2c7b49e97..ee7c3285e3 100644 --- a/ui/src/dataLoaders/components/collectorsWizard/CollectorsWizard.tsx +++ b/ui/src/dataLoaders/components/collectorsWizard/CollectorsWizard.tsx @@ -1,13 +1,29 @@ // Libraries import React, {PureComponent} from 'react' +import Loadable from 'react-loadable' import {connect} from 'react-redux' import {withRouter, WithRouterProps} from 'react-router' -import _ from 'lodash' // Components import {ErrorHandling} from 'src/shared/decorators/errors' import WizardOverlay from 'src/clockface/components/wizard/WizardOverlay' -import CollectorsStepSwitcher from 'src/dataLoaders/components/collectorsWizard/CollectorsStepSwitcher' + +const spinner =
+const TelegrafEditor = Loadable({ + loader: () => import('src/dataLoaders/components/TelegrafEditor'), + loading() { + return spinner + }, +}) +const CollectorsStepSwitcher = Loadable({ + loader: () => + import('src/dataLoaders/components/collectorsWizard/CollectorsStepSwitcher'), + loading() { + return spinner + }, +}) +import {isFlagEnabled, FeatureFlag} from 'src/shared/utils/featureFlag' +import {ComponentColor, Button} from '@influxdata/clockface' // Actions import {notify as notifyAction} from 'src/shared/actions/notifications' @@ -24,11 +40,13 @@ import { setActiveTelegrafPlugin, setPluginConfiguration, } from 'src/dataLoaders/actions/dataLoaders' +import {reset} from 'src/dataLoaders/actions/telegrafEditor' // Types import {Links} from 'src/types/links' import {Substep, TelegrafPlugin} from 'src/types/dataLoaders' -import {AppState, Bucket} from 'src/types' +import {AppState, Bucket, Organization} from 'src/types' +import {downloadTextFile} from 'src/shared/utils/download' export interface CollectorsStepProps { currentStepIndex: number @@ -46,6 +64,7 @@ interface DispatchProps { onSetCurrentStepIndex: typeof setCurrentStepIndex onClearDataLoaders: typeof clearDataLoaders onClearSteps: typeof clearSteps + onClearTelegrafEditor: typeof reset onSetActiveTelegrafPlugin: typeof setActiveTelegrafPlugin onSetPluginConfiguration: typeof setPluginConfiguration } @@ -58,6 +77,8 @@ interface StateProps { substep: Substep username: string bucket: string + text: string + org: Organization } interface State { @@ -65,10 +86,11 @@ interface State { } type Props = StateProps & DispatchProps +type AllProps = Props & WithRouterProps @ErrorHandling -class CollectorsWizard extends PureComponent { - constructor(props) { +class CollectorsWizard extends PureComponent { + constructor(props: AllProps) { super(props) this.state = { buckets: [], @@ -86,8 +108,30 @@ class CollectorsWizard extends PureComponent { +