Merge pull request #1631 from influxdata/dataLoader/streaming-step
feat(ui/DataLoaders): Streaming/Listening steppull/10616/head
commit
2bd3031383
|
@ -3535,7 +3535,7 @@ components:
|
|||
links:
|
||||
readOnly: true
|
||||
$ref: "#/components/schemas/Links"
|
||||
authorizations:
|
||||
auths:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Authorization"
|
||||
|
|
|
@ -159,7 +159,7 @@ export interface Authorizations {
|
|||
* @type {Array<Authorization>}
|
||||
* @memberof Authorizations
|
||||
*/
|
||||
authorizations?: Array<Authorization>;
|
||||
auths?: Array<Authorization>;
|
||||
/**
|
||||
*
|
||||
* @type {Links}
|
||||
|
|
|
@ -70,3 +70,44 @@
|
|||
color: $g11-sidewalk;
|
||||
@include no-user-select();
|
||||
}
|
||||
|
||||
.wizard-step--body, .wizard-step--body-streaming{
|
||||
background-color: $g5-pepper;
|
||||
color: $g20-white;
|
||||
padding: 25px;
|
||||
margin-left: 60px;
|
||||
margin-right: 60px;
|
||||
margin-top: 30px;
|
||||
text-align: left;
|
||||
border-radius: $radius;
|
||||
|
||||
> h6 {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
|
||||
> p {
|
||||
color: $g11-sidewalk;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-step--body-streaming {
|
||||
text-align: center;
|
||||
.loading{
|
||||
color: $c-star
|
||||
}
|
||||
.success{
|
||||
color: $c-rainforest
|
||||
}
|
||||
.error{
|
||||
color: $c-fire
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-step--body-snippet{
|
||||
background-color: $g3-castle;
|
||||
border-radius: $radius;
|
||||
margin: $ix-marg-a 0;
|
||||
padding: $ix-marg-b;
|
||||
font-family: "RobotoMono", monospace;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Types
|
||||
import {DataSource, DataSourceType} from 'src/types/v2/dataSources'
|
||||
|
||||
export type Action = SetDataLoadersType | AddDataSource | RemoveDataSource
|
||||
|
||||
interface SetDataLoadersType {
|
||||
type: 'SET_DATA_LOADERS_TYPE'
|
||||
payload: {type: DataSourceType}
|
||||
}
|
||||
|
||||
export const setDataLoadersType = (
|
||||
type: DataSourceType
|
||||
): SetDataLoadersType => ({
|
||||
type: 'SET_DATA_LOADERS_TYPE',
|
||||
payload: {type},
|
||||
})
|
||||
|
||||
interface AddDataSource {
|
||||
type: 'ADD_DATA_SOURCE'
|
||||
payload: {dataSource: DataSource}
|
||||
}
|
||||
|
||||
export const addDataSource = (dataSource: DataSource): AddDataSource => ({
|
||||
type: 'ADD_DATA_SOURCE',
|
||||
payload: {dataSource},
|
||||
})
|
||||
|
||||
interface RemoveDataSource {
|
||||
type: 'REMOVE_DATA_SOURCE'
|
||||
payload: {dataSource: string}
|
||||
}
|
||||
|
||||
export const removeDataSource = (dataSource: string): RemoveDataSource => ({
|
||||
type: 'REMOVE_DATA_SOURCE',
|
||||
payload: {dataSource},
|
||||
})
|
|
@ -1,50 +0,0 @@
|
|||
// Types
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
|
||||
export type Action =
|
||||
| AddDataSource
|
||||
| RemoveDataSource
|
||||
| SetDataSources
|
||||
| SetActiveDataSource
|
||||
|
||||
interface AddDataSource {
|
||||
type: 'ADD_DATA_SOURCE'
|
||||
payload: {dataSource: DataSource}
|
||||
}
|
||||
|
||||
export const addDataSource = (dataSource: DataSource): AddDataSource => ({
|
||||
type: 'ADD_DATA_SOURCE',
|
||||
payload: {dataSource},
|
||||
})
|
||||
|
||||
interface RemoveDataSource {
|
||||
type: 'REMOVE_DATA_SOURCE'
|
||||
payload: {dataSource: string}
|
||||
}
|
||||
|
||||
export const removeDataSource = (dataSource: string): RemoveDataSource => ({
|
||||
type: 'REMOVE_DATA_SOURCE',
|
||||
payload: {dataSource},
|
||||
})
|
||||
|
||||
interface SetDataSources {
|
||||
type: 'SET_DATA_SOURCES'
|
||||
payload: {dataSources: DataSource[]}
|
||||
}
|
||||
|
||||
export const setDataSources = (dataSources: DataSource[]): SetDataSources => ({
|
||||
type: 'SET_DATA_SOURCES',
|
||||
payload: {dataSources},
|
||||
})
|
||||
|
||||
interface SetActiveDataSource {
|
||||
type: 'SET_ACTIVE_DATA_SOURCE'
|
||||
payload: {dataSource: string}
|
||||
}
|
||||
|
||||
export const setActiveDataSource = (
|
||||
dataSource: string
|
||||
): SetActiveDataSource => ({
|
||||
type: 'SET_ACTIVE_DATA_SOURCE',
|
||||
payload: {dataSource},
|
||||
})
|
|
@ -2,8 +2,10 @@ import _ from 'lodash'
|
|||
|
||||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
import {telegrafsAPI, authorizationsAPI} from 'src/utils/api'
|
||||
import {Telegraf, TelegrafRequest, TelegrafRequestPlugins} from 'src/api'
|
||||
import {telegrafsApi} from 'src/utils/api'
|
||||
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
export const getSetupStatus = async (url: string): Promise<boolean> => {
|
||||
try {
|
||||
|
@ -28,7 +30,7 @@ export const getTelegrafConfigTOML = async (
|
|||
},
|
||||
}
|
||||
|
||||
const response = await telegrafsApi.telegrafsTelegrafIDGet(
|
||||
const response = await telegrafsAPI.telegrafsTelegrafIDGet(
|
||||
telegrafID,
|
||||
options
|
||||
)
|
||||
|
@ -49,7 +51,7 @@ export const createTelegrafConfig = async (): Promise<Telegraf> => {
|
|||
},
|
||||
],
|
||||
}
|
||||
const {data} = await telegrafsApi.telegrafsPost('123', telegrafRequest)
|
||||
const {data} = await telegrafsAPI.telegrafsPost('123', telegrafRequest)
|
||||
return data
|
||||
}
|
||||
|
||||
|
@ -108,3 +110,24 @@ export const trySources = async (url: string): Promise<boolean> => {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const getTelegrafConfigs = async (org: string): Promise<Telegraf[]> => {
|
||||
try {
|
||||
const data = await telegrafsAPI.telegrafsGet(org)
|
||||
|
||||
return getDeep<Telegraf[]>(data, 'data.configurations', [])
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
export const getAuthorizationToken = async (
|
||||
username: string
|
||||
): Promise<string> => {
|
||||
try {
|
||||
const data = await authorizationsAPI.authorizationsGet(undefined, username)
|
||||
return getDeep<string>(data, 'data.auths.0.token', '')
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import {telegrafConfigsResponse, authResponse} from 'src/onboarding/resources'
|
||||
|
||||
const telegrafsGet = jest.fn(() => Promise.resolve(telegrafConfigsResponse))
|
||||
const authorizationsGet = jest.fn(() => Promise.resolve(authResponse))
|
||||
|
||||
export const telegrafsAPI = {
|
||||
telegrafsGet,
|
||||
}
|
||||
|
||||
export const authorizationsAPI = {
|
||||
authorizationsGet,
|
||||
}
|
|
@ -1,7 +1,18 @@
|
|||
import {getSetupStatus, setSetupParams, SetupParams} from 'src/onboarding/apis'
|
||||
import {
|
||||
getSetupStatus,
|
||||
setSetupParams,
|
||||
SetupParams,
|
||||
getTelegrafConfigs,
|
||||
getAuthorizationToken,
|
||||
} from 'src/onboarding/apis'
|
||||
|
||||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
import {telegrafConfig, token} from 'src/onboarding/resources'
|
||||
import {telegrafsAPI, authorizationsAPI} from 'src/onboarding/apis/mocks'
|
||||
|
||||
jest.mock('src/utils/ajax', () => require('mocks/utils/ajax'))
|
||||
jest.mock('src/utils/api', () => require('src/onboarding/apis/mocks'))
|
||||
|
||||
describe('Onboarding.Apis', () => {
|
||||
afterEach(() => {
|
||||
|
@ -36,4 +47,27 @@ describe('Onboarding.Apis', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getTelegrafConfigs', () => {
|
||||
it('should return an array of configs', async () => {
|
||||
const org = 'default'
|
||||
const result = await getTelegrafConfigs(org)
|
||||
|
||||
expect(result).toEqual([telegrafConfig])
|
||||
expect(telegrafsAPI.telegrafsGet).toBeCalledWith(org)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAuthorizationToken', () => {
|
||||
it('should return a token', async () => {
|
||||
const username = 'iris'
|
||||
const result = await getAuthorizationToken(username)
|
||||
|
||||
expect(result).toEqual(token)
|
||||
expect(authorizationsAPI.authorizationsGet).toBeCalledWith(
|
||||
undefined,
|
||||
username
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {
|
||||
Button,
|
||||
ComponentColor,
|
||||
ComponentSize,
|
||||
ComponentStatus,
|
||||
} from 'src/clockface'
|
||||
import ConfigureDataSourceSwitcher from 'src/onboarding/components/ConfigureDataSourceSwitcher'
|
||||
|
||||
// Types
|
||||
import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard'
|
||||
import {DataSource, DataSourceType} from 'src/types/v2/dataSources'
|
||||
|
||||
export interface Props extends OnboardingStepProps {
|
||||
dataSources: DataSource[]
|
||||
type: DataSourceType
|
||||
}
|
||||
|
||||
interface State {
|
||||
currentDataSourceIndex: number
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ConfigureDataSourceStep extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
currentDataSourceIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {setupParams, dataSources, notify} = this.props
|
||||
|
||||
return (
|
||||
<div className="onboarding-step">
|
||||
<ConfigureDataSourceSwitcher
|
||||
dataSources={dataSources}
|
||||
currentIndex={this.state.currentDataSourceIndex}
|
||||
org={_.get(setupParams, 'org', '')}
|
||||
username={_.get(setupParams, 'username', '')}
|
||||
bucket={_.get(setupParams, 'bucket', '')}
|
||||
notify={notify}
|
||||
/>
|
||||
|
||||
<div className="wizard-button-bar">
|
||||
<Button
|
||||
color={ComponentColor.Default}
|
||||
text="Back"
|
||||
size={ComponentSize.Medium}
|
||||
onClick={this.handlePrevious}
|
||||
/>
|
||||
<Button
|
||||
color={ComponentColor.Primary}
|
||||
text="Next"
|
||||
size={ComponentSize.Medium}
|
||||
onClick={this.handleNext}
|
||||
status={ComponentStatus.Default}
|
||||
titleText={'Next'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleNext = () => {
|
||||
const {onIncrementCurrentStepIndex, dataSources} = this.props
|
||||
const {currentDataSourceIndex} = this.state
|
||||
|
||||
if (currentDataSourceIndex >= dataSources.length) {
|
||||
onIncrementCurrentStepIndex()
|
||||
} else {
|
||||
this.setState({currentDataSourceIndex: currentDataSourceIndex + 1})
|
||||
}
|
||||
}
|
||||
|
||||
private handlePrevious = () => {
|
||||
const {onDecrementCurrentStepIndex} = this.props
|
||||
const {currentDataSourceIndex} = this.state
|
||||
|
||||
if (currentDataSourceIndex === 0) {
|
||||
onDecrementCurrentStepIndex()
|
||||
} else {
|
||||
this.setState({currentDataSourceIndex: currentDataSourceIndex - 1})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigureDataSourceStep
|
|
@ -10,28 +10,55 @@ import {getTelegrafConfigTOML, createTelegrafConfig} from 'src/onboarding/apis'
|
|||
// Components
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Button} from 'src/clockface'
|
||||
import DataStreaming from 'src/onboarding/components/DataStreaming'
|
||||
|
||||
// Constants
|
||||
import {getTelegrafConfigFailed} from 'src/shared/copy/v2/notifications'
|
||||
|
||||
// Types
|
||||
import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard'
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
import {NotificationAction} from 'src/types'
|
||||
|
||||
export interface Props extends OnboardingStepProps {
|
||||
dataSource: string
|
||||
export interface Props {
|
||||
dataSources: DataSource[]
|
||||
currentIndex: number
|
||||
org: string
|
||||
username: string
|
||||
bucket: string
|
||||
notify: NotificationAction
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ConfigureDataSourceSwitcher extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {dataSource} = this.props
|
||||
const {org, bucket, username} = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
{dataSource}
|
||||
<Button text="Click to Download Config" onClick={this.handleDownload} />
|
||||
</div>
|
||||
)
|
||||
switch (this.configurationStep) {
|
||||
case 'Listening':
|
||||
return <DataStreaming org={org} username={username} bucket={bucket} />
|
||||
case 'CSV':
|
||||
case 'Line Protocol':
|
||||
default:
|
||||
return (
|
||||
<div>
|
||||
{this.configurationStep}
|
||||
<Button
|
||||
text="Click to Download Config"
|
||||
onClick={this.handleDownload}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private get configurationStep() {
|
||||
const {currentIndex, dataSources} = this.props
|
||||
|
||||
if (currentIndex === dataSources.length) {
|
||||
return 'Listening'
|
||||
}
|
||||
|
||||
return dataSources[currentIndex].name
|
||||
}
|
||||
|
||||
private handleDownload = async () => {
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Libraries
|
||||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
// Components
|
||||
import ConnectionInformation from 'src/onboarding/components/ConnectionInformation'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
const setup = (override = {}) => {
|
||||
const props = {
|
||||
loading: RemoteDataState.NotStarted,
|
||||
bucket: 'defbuck',
|
||||
...override,
|
||||
}
|
||||
|
||||
const wrapper = shallow(<ConnectionInformation {...props} />)
|
||||
|
||||
return {wrapper}
|
||||
}
|
||||
|
||||
describe('Onboarding.Components.ConnectionInformation', () => {
|
||||
it('renders', () => {
|
||||
const {wrapper} = setup()
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('matches snapshot if loading', () => {
|
||||
const {wrapper} = setup({loading: RemoteDataState.Loading})
|
||||
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('matches snapshot if success', () => {
|
||||
const {wrapper} = setup({loading: RemoteDataState.Done})
|
||||
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('matches snapshot if error', () => {
|
||||
const {wrapper} = setup({loading: RemoteDataState.Error})
|
||||
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,61 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Decorator
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
export interface Props {
|
||||
loading: RemoteDataState
|
||||
bucket: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ListeningResults extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<h4 className={this.className}>{this.header}</h4>
|
||||
<p>{this.additionalText}</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private get className(): string {
|
||||
switch (this.props.loading) {
|
||||
case RemoteDataState.Loading:
|
||||
return 'loading'
|
||||
case RemoteDataState.Done:
|
||||
return 'success'
|
||||
case RemoteDataState.Error:
|
||||
return 'error'
|
||||
}
|
||||
}
|
||||
|
||||
private get header(): string {
|
||||
switch (this.props.loading) {
|
||||
case RemoteDataState.Loading:
|
||||
return 'Awaiting Connection...'
|
||||
case RemoteDataState.Done:
|
||||
return 'Connection Found!'
|
||||
case RemoteDataState.Error:
|
||||
return 'Connection Not Found'
|
||||
}
|
||||
}
|
||||
|
||||
private get additionalText(): string {
|
||||
switch (this.props.loading) {
|
||||
case RemoteDataState.Loading:
|
||||
return 'Timeout in 60 seconds'
|
||||
case RemoteDataState.Done:
|
||||
return `${this.props.bucket} is recieving data load and clear!`
|
||||
case RemoteDataState.Error:
|
||||
return 'Check config and try again'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ListeningResults
|
|
@ -0,0 +1,43 @@
|
|||
// Libraries
|
||||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
// Components
|
||||
import DataListening from 'src/onboarding/components/DataListening'
|
||||
import ConnectionInformation from 'src/onboarding/components/ConnectionInformation'
|
||||
import {Button} from 'src/clockface'
|
||||
|
||||
const setup = (override = {}) => {
|
||||
const props = {
|
||||
bucket: 'defbuck',
|
||||
...override,
|
||||
}
|
||||
|
||||
const wrapper = shallow(<DataListening {...props} />)
|
||||
|
||||
return {wrapper}
|
||||
}
|
||||
|
||||
describe('Onboarding.Components.DataListening', () => {
|
||||
it('renders', () => {
|
||||
const {wrapper} = setup()
|
||||
const button = wrapper.find(Button)
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
expect(button.exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('if button is clicked', () => {
|
||||
it('displays connection information', () => {
|
||||
const {wrapper} = setup()
|
||||
|
||||
const button = wrapper.find(Button)
|
||||
button.simulate('click')
|
||||
|
||||
const connectionInfo = wrapper.find(ConnectionInformation)
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
expect(connectionInfo.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,134 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Apis
|
||||
import {executeQuery} from 'src/shared/apis/v2/query'
|
||||
|
||||
// Components
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {
|
||||
Button,
|
||||
ComponentColor,
|
||||
ComponentSize,
|
||||
ComponentStatus,
|
||||
} from 'src/clockface'
|
||||
import ConnectionInformation from 'src/onboarding/components/ConnectionInformation'
|
||||
|
||||
// types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import {InfluxLanguage} from 'src/types/v2/dashboards'
|
||||
|
||||
export interface Props {
|
||||
bucket: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: RemoteDataState
|
||||
}
|
||||
|
||||
const MINUTE = 60000
|
||||
const WAIT = 5000
|
||||
|
||||
@ErrorHandling
|
||||
class DataListening extends PureComponent<Props, State> {
|
||||
private intervalID: NodeJS.Timer
|
||||
private startTime: number
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {loading: RemoteDataState.NotStarted}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
clearInterval(this.intervalID)
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="wizard-step--body-streaming">
|
||||
{this.connectionInfo}
|
||||
{this.listenButton}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get connectionInfo(): JSX.Element {
|
||||
const {loading} = this.state
|
||||
|
||||
if (loading === RemoteDataState.NotStarted) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectionInformation
|
||||
loading={this.state.loading}
|
||||
bucket={this.props.bucket}
|
||||
/>
|
||||
)
|
||||
}
|
||||
private get listenButton(): JSX.Element {
|
||||
const {loading} = this.state
|
||||
|
||||
if (
|
||||
loading === RemoteDataState.Loading ||
|
||||
loading === RemoteDataState.Done
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color={ComponentColor.Primary}
|
||||
text="Listen for Data"
|
||||
size={ComponentSize.Medium}
|
||||
onClick={this.handleClick}
|
||||
status={ComponentStatus.Default}
|
||||
titleText={'Listen for Data'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private handleClick = (): void => {
|
||||
this.setState({loading: RemoteDataState.Loading})
|
||||
this.startTime = Number(new Date())
|
||||
this.checkForData()
|
||||
}
|
||||
|
||||
private checkForData = async (): Promise<void> => {
|
||||
const {bucket} = this.props
|
||||
const script = `from(bucket: "${bucket}")
|
||||
|> range(start: -1m)`
|
||||
|
||||
let rowCount
|
||||
let timePassed
|
||||
|
||||
try {
|
||||
const response = await executeQuery(
|
||||
'/api/v2/query',
|
||||
script,
|
||||
InfluxLanguage.Flux
|
||||
)
|
||||
rowCount = response.rowCount
|
||||
timePassed = Number(new Date()) - this.startTime
|
||||
} catch (err) {
|
||||
this.setState({loading: RemoteDataState.Error})
|
||||
return
|
||||
}
|
||||
|
||||
if (rowCount > 1) {
|
||||
this.setState({loading: RemoteDataState.Done})
|
||||
return
|
||||
}
|
||||
|
||||
if (timePassed >= MINUTE) {
|
||||
this.setState({loading: RemoteDataState.Error})
|
||||
return
|
||||
}
|
||||
|
||||
this.intervalID = setTimeout(this.checkForData, WAIT)
|
||||
}
|
||||
}
|
||||
|
||||
export default DataListening
|
|
@ -7,19 +7,21 @@ import CardSelectCard from 'src/clockface/components/card_select/CardSelectCard'
|
|||
import GridSizer from 'src/clockface/components/grid_sizer/GridSizer'
|
||||
|
||||
// Types
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
import {StreamingOptions} from 'src/onboarding/components/SelectDataSourceStep'
|
||||
import {DataSourceType} from 'src/types/v2/dataSources'
|
||||
|
||||
export interface Props {
|
||||
dataSources: DataSource[]
|
||||
onSelectDataSource: (dataSource: string) => void
|
||||
streaming: StreamingOptions
|
||||
type: DataSourceType
|
||||
}
|
||||
|
||||
const DATA_SOURCES_OPTIONS = ['CSV', 'Streaming', 'Line Protocol']
|
||||
const DATA_SOURCES_OPTIONS = [
|
||||
DataSourceType.CSV,
|
||||
DataSourceType.Streaming,
|
||||
DataSourceType.LineProtocol,
|
||||
]
|
||||
|
||||
@ErrorHandling
|
||||
class DataSourceSelector extends PureComponent<Props> {
|
||||
class DataSourceTypeSelector extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<GridSizer>
|
||||
|
@ -31,7 +33,7 @@ class DataSourceSelector extends PureComponent<Props> {
|
|||
name={ds}
|
||||
label={ds}
|
||||
checked={this.isCardChecked(ds)}
|
||||
onClick={this.handleToggle(ds)}
|
||||
onClick={this.handleClick(ds)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
@ -39,21 +41,15 @@ class DataSourceSelector extends PureComponent<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
private isCardChecked(dataSource: string) {
|
||||
const {dataSources, streaming} = this.props
|
||||
if (dataSource === 'Streaming') {
|
||||
return streaming === StreamingOptions.Selected
|
||||
}
|
||||
private isCardChecked(dataSource: DataSourceType) {
|
||||
const {type} = this.props
|
||||
|
||||
if (dataSources.find(ds => ds.name === dataSource)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return dataSource === type
|
||||
}
|
||||
|
||||
private handleToggle = (dataSource: string) => () => {
|
||||
private handleClick = (dataSource: string) => () => {
|
||||
this.props.onSelectDataSource(dataSource)
|
||||
}
|
||||
}
|
||||
|
||||
export default DataSourceSelector
|
||||
export default DataSourceTypeSelector
|
|
@ -0,0 +1,47 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import TelegrafInstructions from 'src/onboarding/components/TelegrafInstructions'
|
||||
import FetchConfigID from 'src/onboarding/components/FetchConfigID'
|
||||
import FetchAuthToken from 'src/onboarding/components/FetchAuthToken'
|
||||
import DataListening from 'src/onboarding/components/DataListening'
|
||||
|
||||
// Decorator
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
bucket: string
|
||||
org: string
|
||||
username: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class DataStreaming extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<FetchConfigID org={this.props.org}>
|
||||
{configID => (
|
||||
<FetchAuthToken
|
||||
bucket={this.props.bucket}
|
||||
username={this.props.username}
|
||||
>
|
||||
{authToken => (
|
||||
<TelegrafInstructions
|
||||
authToken={authToken}
|
||||
configID={configID}
|
||||
/>
|
||||
)}
|
||||
</FetchAuthToken>
|
||||
)}
|
||||
</FetchConfigID>
|
||||
|
||||
<DataListening bucket={this.props.bucket} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DataStreaming
|
|
@ -0,0 +1,28 @@
|
|||
// Libraries
|
||||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
// Components
|
||||
import FetchAuthToken from 'src/onboarding/components/FetchAuthToken'
|
||||
|
||||
jest.mock('src/utils/api', () => require('src/onboarding/apis/mocks'))
|
||||
|
||||
const setup = async (override = {}) => {
|
||||
const props = {
|
||||
bucket: '',
|
||||
username: '',
|
||||
children: jest.fn(),
|
||||
...override,
|
||||
}
|
||||
|
||||
const wrapper = await shallow(<FetchAuthToken {...props} />)
|
||||
|
||||
return {wrapper}
|
||||
}
|
||||
|
||||
describe('FetchAuthToken', () => {
|
||||
it('renders', async () => {
|
||||
const {wrapper} = await setup()
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,51 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {Spinner} from 'src/clockface'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Apis
|
||||
import {getAuthorizationToken} from 'src/onboarding/apis/index'
|
||||
|
||||
// types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
export interface Props {
|
||||
bucket: string
|
||||
username: string
|
||||
children: (authToken: string) => JSX.Element
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: RemoteDataState
|
||||
authToken?: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class FetchAuthToken extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {loading: RemoteDataState.NotStarted}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {username} = this.props
|
||||
|
||||
this.setState({loading: RemoteDataState.Loading})
|
||||
const authToken = await getAuthorizationToken(username)
|
||||
this.setState({authToken, loading: RemoteDataState.Done})
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Spinner loading={this.state.loading}>
|
||||
{this.props.children(this.state.authToken)}
|
||||
</Spinner>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FetchAuthToken
|
|
@ -0,0 +1,27 @@
|
|||
// Libraries
|
||||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
// Components
|
||||
import FetchConfigID from 'src/onboarding/components/FetchConfigID'
|
||||
|
||||
jest.mock('src/utils/api', () => require('src/onboarding/apis/mocks'))
|
||||
|
||||
const setup = async (override = {}) => {
|
||||
const props = {
|
||||
org: 'default',
|
||||
children: jest.fn(),
|
||||
...override,
|
||||
}
|
||||
|
||||
const wrapper = await shallow(<FetchConfigID {...props} />)
|
||||
|
||||
return {wrapper}
|
||||
}
|
||||
|
||||
describe('FetchConfigID', () => {
|
||||
it('renders', async () => {
|
||||
const {wrapper} = await setup()
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,51 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {Spinner} from 'src/clockface'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Apis
|
||||
import {getTelegrafConfigs} from 'src/onboarding/apis/index'
|
||||
|
||||
// types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
export interface Props {
|
||||
org: string
|
||||
children: (configID: string) => JSX.Element
|
||||
}
|
||||
|
||||
interface State {
|
||||
loading: RemoteDataState
|
||||
configID?: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class FetchConfigID extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {loading: RemoteDataState.NotStarted}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {org} = this.props
|
||||
|
||||
this.setState({loading: RemoteDataState.Loading})
|
||||
const telegrafConfigs = await getTelegrafConfigs(org)
|
||||
const configID = _.get(telegrafConfigs, '0.id', '')
|
||||
this.setState({configID, loading: RemoteDataState.Done})
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<Spinner loading={this.state.loading}>
|
||||
{this.props.children(this.state.configID)}
|
||||
</Spinner>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FetchConfigID
|
|
@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||
import InitStep from 'src/onboarding/components/InitStep'
|
||||
import AdminStep from 'src/onboarding/components/AdminStep'
|
||||
import SelectDataSourceStep from 'src/onboarding/components/SelectDataSourceStep'
|
||||
import ConfigureDataSourceSwitcher from 'src/onboarding/components/ConfigureDataSourceSwitcher'
|
||||
import ConfigureDataSourceStep from 'src/onboarding/components/ConfigureDataSourceStep'
|
||||
import CompletionStep from 'src/onboarding/components/CompletionStep'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
|
@ -14,21 +14,21 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
import {
|
||||
addDataSource,
|
||||
removeDataSource,
|
||||
setDataSources,
|
||||
} from 'src/onboarding/actions/dataSources'
|
||||
setDataLoadersType,
|
||||
} from 'src/onboarding/actions/dataLoaders'
|
||||
|
||||
// Types
|
||||
import {SetupParams} from 'src/onboarding/apis'
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
import {DataSource, DataSourceType} from 'src/types/v2/dataSources'
|
||||
import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard'
|
||||
|
||||
interface Props {
|
||||
onboardingStepProps: OnboardingStepProps
|
||||
onAddDataSource: typeof addDataSource
|
||||
onRemoveDataSource: typeof removeDataSource
|
||||
onSetDataSources: typeof setDataSources
|
||||
onSetDataLoadersType: typeof setDataLoadersType
|
||||
setupParams: SetupParams
|
||||
dataSources: DataSource[]
|
||||
dataLoaders: {dataSources: DataSource[]; type: DataSourceType}
|
||||
stepTitle: string
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,10 @@ class OnboardingStepSwitcher extends PureComponent<Props> {
|
|||
onboardingStepProps,
|
||||
stepTitle,
|
||||
setupParams,
|
||||
dataSources,
|
||||
dataLoaders,
|
||||
onSetDataLoadersType,
|
||||
onAddDataSource,
|
||||
onRemoveDataSource,
|
||||
onSetDataSources,
|
||||
} = this.props
|
||||
|
||||
switch (stepTitle) {
|
||||
|
@ -54,19 +54,16 @@ class OnboardingStepSwitcher extends PureComponent<Props> {
|
|||
return (
|
||||
<SelectDataSourceStep
|
||||
{...onboardingStepProps}
|
||||
{...dataLoaders}
|
||||
onSetDataLoadersType={onSetDataLoadersType}
|
||||
bucket={_.get(setupParams, 'bucket', '')}
|
||||
dataSources={dataSources}
|
||||
onAddDataSource={onAddDataSource}
|
||||
onRemoveDataSource={onRemoveDataSource}
|
||||
onSetDataSources={onSetDataSources}
|
||||
/>
|
||||
)
|
||||
case 'Configure Data Sources':
|
||||
return (
|
||||
<ConfigureDataSourceSwitcher
|
||||
{...onboardingStepProps}
|
||||
dataSource={_.get(dataSources, '0.name', '')}
|
||||
/>
|
||||
<ConfigureDataSourceStep {...onboardingStepProps} {...dataLoaders} />
|
||||
)
|
||||
case 'Complete':
|
||||
return <CompletionStep {...onboardingStepProps} />
|
||||
|
|
|
@ -9,29 +9,28 @@ import {
|
|||
ComponentSize,
|
||||
ComponentStatus,
|
||||
} from 'src/clockface'
|
||||
import DataSourceSelector from 'src/onboarding/components/DataSourceSelector'
|
||||
import DataSourceTypeSelector from 'src/onboarding/components/DataSourceTypeSelector'
|
||||
import StreamingDataSourceSelector from 'src/onboarding/components/StreamingDataSourcesSelector'
|
||||
|
||||
// Types
|
||||
import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard'
|
||||
import {DataSource, ConfigurationState} from 'src/types/v2/dataSources'
|
||||
import {
|
||||
DataSource,
|
||||
DataSourceType,
|
||||
ConfigurationState,
|
||||
} from 'src/types/v2/dataSources'
|
||||
|
||||
export interface Props extends OnboardingStepProps {
|
||||
bucket: string
|
||||
dataSources: DataSource[]
|
||||
type: DataSourceType
|
||||
onAddDataSource: (dataSource: DataSource) => void
|
||||
onRemoveDataSource: (dataSource: string) => void
|
||||
onSetDataSources: (dataSources: DataSource[]) => void
|
||||
}
|
||||
|
||||
export enum StreamingOptions {
|
||||
NotSelected = 'not selected',
|
||||
Selected = 'selected',
|
||||
Show = 'show',
|
||||
onSetDataLoadersType: (type: DataSourceType) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
streaming: StreamingOptions
|
||||
showStreamingSources: boolean
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -39,7 +38,7 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
|
|||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.state = {streaming: StreamingOptions.NotSelected}
|
||||
this.state = {showStreamingSources: false}
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -77,7 +76,10 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private get selector(): JSX.Element {
|
||||
if (this.state.streaming === StreamingOptions.Show) {
|
||||
if (
|
||||
this.props.type === DataSourceType.Streaming &&
|
||||
this.state.showStreamingSources
|
||||
) {
|
||||
return (
|
||||
<StreamingDataSourceSelector
|
||||
dataSources={this.props.dataSources}
|
||||
|
@ -86,17 +88,19 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
|
|||
)
|
||||
}
|
||||
return (
|
||||
<DataSourceSelector
|
||||
dataSources={this.props.dataSources}
|
||||
<DataSourceTypeSelector
|
||||
onSelectDataSource={this.handleSelectDataSource}
|
||||
streaming={this.state.streaming}
|
||||
type={this.props.type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private handleClickNext = () => {
|
||||
if (this.state.streaming === StreamingOptions.Selected) {
|
||||
this.setState({streaming: StreamingOptions.Show})
|
||||
if (
|
||||
this.props.type === DataSourceType.Streaming &&
|
||||
!this.state.showStreamingSources
|
||||
) {
|
||||
this.setState({showStreamingSources: true})
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -104,32 +108,17 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private handleClickBack = () => {
|
||||
if (this.state.streaming === StreamingOptions.Show) {
|
||||
this.setState({streaming: StreamingOptions.NotSelected})
|
||||
if (this.props.type === DataSourceType.Streaming) {
|
||||
this.setState({showStreamingSources: false})
|
||||
return
|
||||
}
|
||||
|
||||
this.props.onDecrementCurrentStepIndex()
|
||||
}
|
||||
|
||||
private handleSelectDataSource = (dataSource: string) => {
|
||||
switch (dataSource) {
|
||||
case 'Streaming':
|
||||
this.setState({streaming: StreamingOptions.Selected})
|
||||
this.props.onSetDataSources([])
|
||||
break
|
||||
default:
|
||||
this.setState({streaming: StreamingOptions.NotSelected})
|
||||
this.props.onSetDataSources([
|
||||
{
|
||||
name: dataSource,
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
])
|
||||
break
|
||||
}
|
||||
private handleSelectDataSource = (dataSource: DataSourceType) => {
|
||||
this.props.onSetDataLoadersType(dataSource)
|
||||
return
|
||||
}
|
||||
|
||||
private handleToggleDataSource = (
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
import TelegrafInstructions from 'src/onboarding/components/TelegrafInstructions'
|
||||
|
||||
let wrapper
|
||||
|
||||
const setup = (override = {}) => {
|
||||
const props = {
|
||||
authToken: '',
|
||||
configID: '',
|
||||
...override,
|
||||
}
|
||||
|
||||
return shallow(<TelegrafInstructions {...props} />)
|
||||
}
|
||||
|
||||
describe('TelegrafInstructions', () => {
|
||||
it('renders', async () => {
|
||||
const wrapper = await setup()
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('matches snapshot', () => {
|
||||
wrapper = setup()
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Decorator
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
|
||||
export interface Props {
|
||||
authToken: string
|
||||
configID: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class TelegrafInstructions extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<h3 className="wizard-step--title">Listen for Streaming Data</h3>
|
||||
<h5 className="wizard-step--sub-title">
|
||||
You have selected streaming data sources. Follow the instructions
|
||||
below to begin listening for incoming data.
|
||||
</h5>
|
||||
<div className="wizard-step--body">
|
||||
<h6>Install</h6>
|
||||
<p>
|
||||
You can download the binaries directly from the downloads page or
|
||||
from the releases section.{' '}
|
||||
</p>
|
||||
<h6>Start Data Stream</h6>
|
||||
<p>
|
||||
After installing the telegraf client, save this environment
|
||||
variable. run the following command.
|
||||
</p>
|
||||
<p className="wizard-step--body-snippet">export INFLUX_TOKEN={this.props.authToken}</p>
|
||||
<p>Run the following command.</p>
|
||||
<p className="wizard-step--body-snippet">
|
||||
telegraf -config http://localhost:9999/api/v2/telegrafs/{this.props.configID}
|
||||
</p>
|
||||
</div>
|
||||
</>)
|
||||
}
|
||||
}
|
||||
|
||||
export default TelegrafInstructions
|
|
@ -0,0 +1,40 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Onboarding.Components.ConnectionInformation matches snapshot if error 1`] = `
|
||||
<Fragment>
|
||||
<h4
|
||||
className="error"
|
||||
>
|
||||
Connection Not Found
|
||||
</h4>
|
||||
<p>
|
||||
Check config and try again
|
||||
</p>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Onboarding.Components.ConnectionInformation matches snapshot if loading 1`] = `
|
||||
<Fragment>
|
||||
<h4
|
||||
className="loading"
|
||||
>
|
||||
Awaiting Connection...
|
||||
</h4>
|
||||
<p>
|
||||
Timeout in 60 seconds
|
||||
</p>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Onboarding.Components.ConnectionInformation matches snapshot if success 1`] = `
|
||||
<Fragment>
|
||||
<h4
|
||||
className="success"
|
||||
>
|
||||
Connection Found!
|
||||
</h4>
|
||||
<p>
|
||||
defbuck is recieving data load and clear!
|
||||
</p>
|
||||
</Fragment>
|
||||
`;
|
|
@ -0,0 +1,46 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TelegrafInstructions matches snapshot 1`] = `
|
||||
<Fragment>
|
||||
<h3
|
||||
className="wizard-step--title"
|
||||
>
|
||||
Listen for Streaming Data
|
||||
</h3>
|
||||
<h5
|
||||
className="wizard-step--sub-title"
|
||||
>
|
||||
You have selected streaming data sources. Follow the instructions below to begin listening for incoming data.
|
||||
</h5>
|
||||
<div
|
||||
className="wizard-step--body"
|
||||
>
|
||||
<h6>
|
||||
Install
|
||||
</h6>
|
||||
<p>
|
||||
You can download the binaries directly from the downloads page or from the releases section.
|
||||
|
||||
</p>
|
||||
<h6>
|
||||
Start Data Stream
|
||||
</h6>
|
||||
<p>
|
||||
After installing the telegraf client, save this environment variable. run the following command.
|
||||
</p>
|
||||
<p
|
||||
className="wizard-step--body-snippet"
|
||||
>
|
||||
export INFLUX_TOKEN=
|
||||
</p>
|
||||
<p>
|
||||
Run the following command.
|
||||
</p>
|
||||
<p
|
||||
className="wizard-step--body-snippet"
|
||||
>
|
||||
telegraf -config http://localhost:9999/api/v2/telegrafs/
|
||||
</p>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
|
@ -24,10 +24,10 @@ import {
|
|||
setStepStatus,
|
||||
} from 'src/onboarding/actions/steps'
|
||||
import {
|
||||
setDataLoadersType,
|
||||
addDataSource,
|
||||
removeDataSource,
|
||||
setDataSources,
|
||||
} from 'src/onboarding/actions/dataSources'
|
||||
} from 'src/onboarding/actions/dataLoaders'
|
||||
|
||||
// Constants
|
||||
import {StepStatus} from 'src/clockface/constants/wizard'
|
||||
|
@ -35,7 +35,7 @@ import {StepStatus} from 'src/clockface/constants/wizard'
|
|||
// Types
|
||||
import {Links} from 'src/types/v2/links'
|
||||
import {SetupParams} from 'src/onboarding/apis'
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
import {DataSource, DataSourceType} from 'src/types/v2/dataSources'
|
||||
import {Notification, NotificationFunc} from 'src/types'
|
||||
import {AppState} from 'src/types/v2'
|
||||
|
||||
|
@ -68,9 +68,14 @@ interface DispatchProps {
|
|||
onDecrementCurrentStepIndex: typeof decrementCurrentStepIndex
|
||||
onSetCurrentStepIndex: typeof setCurrentStepIndex
|
||||
onSetStepStatus: typeof setStepStatus
|
||||
onSetDataLoadersType: typeof setDataLoadersType
|
||||
onAddDataSource: typeof addDataSource
|
||||
onRemoveDataSource: typeof removeDataSource
|
||||
onSetDataSources: typeof setDataSources
|
||||
}
|
||||
|
||||
interface DataLoadersProps {
|
||||
dataSources: DataSource[]
|
||||
type: DataSourceType
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
|
@ -78,7 +83,7 @@ interface StateProps {
|
|||
currentStepIndex: number
|
||||
stepStatuses: StepStatus[]
|
||||
setupParams: SetupParams
|
||||
dataSources: DataSource[]
|
||||
dataLoaders: DataLoadersProps
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & DispatchProps & WithRouterProps
|
||||
|
@ -97,18 +102,15 @@ class OnboardingWizard extends PureComponent<Props> {
|
|||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
dataSources: [],
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
currentStepIndex,
|
||||
dataSources,
|
||||
dataLoaders,
|
||||
onSetDataLoadersType,
|
||||
onRemoveDataSource,
|
||||
onAddDataSource,
|
||||
onSetDataSources,
|
||||
setupParams,
|
||||
} = this.props
|
||||
const currentStepTitle = this.stepTitles[currentStepIndex]
|
||||
|
@ -121,10 +123,10 @@ class OnboardingWizard extends PureComponent<Props> {
|
|||
onboardingStepProps={this.onboardingStepProps}
|
||||
stepTitle={currentStepTitle}
|
||||
setupParams={setupParams}
|
||||
dataSources={dataSources}
|
||||
dataLoaders={dataLoaders}
|
||||
onSetDataLoadersType={onSetDataLoadersType}
|
||||
onAddDataSource={onAddDataSource}
|
||||
onRemoveDataSource={onRemoveDataSource}
|
||||
onSetDataSources={onSetDataSources}
|
||||
/>
|
||||
</div>
|
||||
</WizardFullScreen>
|
||||
|
@ -205,14 +207,14 @@ const mstp = ({
|
|||
links,
|
||||
onboarding: {
|
||||
steps: {currentStepIndex, stepStatuses, setupParams},
|
||||
dataSources,
|
||||
dataLoaders,
|
||||
},
|
||||
}: AppState): StateProps => ({
|
||||
links,
|
||||
currentStepIndex,
|
||||
stepStatuses,
|
||||
setupParams,
|
||||
dataSources,
|
||||
dataLoaders,
|
||||
})
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
|
@ -222,9 +224,9 @@ const mdtp: DispatchProps = {
|
|||
onIncrementCurrentStepIndex: incrementCurrentStepIndex,
|
||||
onSetCurrentStepIndex: setCurrentStepIndex,
|
||||
onSetStepStatus: setStepStatus,
|
||||
onSetDataLoadersType: setDataLoadersType,
|
||||
onAddDataSource: addDataSource,
|
||||
onRemoveDataSource: removeDataSource,
|
||||
onSetDataSources: setDataSources,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, OwnProps>(
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
// Reducer
|
||||
import dataLoadersReducer, {
|
||||
// DataLoadersState,
|
||||
INITIAL_STATE,
|
||||
} from 'src/onboarding/reducers/dataLoaders'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
setDataLoadersType,
|
||||
addDataSource,
|
||||
removeDataSource,
|
||||
} from 'src/onboarding/actions/dataLoaders'
|
||||
|
||||
import {DataSourceType, ConfigurationState} from 'src/types/v2/dataSources'
|
||||
|
||||
describe('dataLoader reducer', () => {
|
||||
describe('if type is streaming', () => {
|
||||
it('can set a type', () => {
|
||||
const actual = dataLoadersReducer(
|
||||
INITIAL_STATE,
|
||||
setDataLoadersType(DataSourceType.Streaming)
|
||||
)
|
||||
const expected = {dataSources: [], type: DataSourceType.Streaming}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('if type is not streaming', () => {
|
||||
it('cant set a type not streaming', () => {
|
||||
const actual = dataLoadersReducer(
|
||||
INITIAL_STATE,
|
||||
setDataLoadersType(DataSourceType.CSV)
|
||||
)
|
||||
const expected = {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'CSV',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
],
|
||||
type: DataSourceType.CSV,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('if data source is added', () => {
|
||||
it('can add a data source', () => {
|
||||
const actual = dataLoadersReducer(
|
||||
INITIAL_STATE,
|
||||
addDataSource({
|
||||
name: 'CSV',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
})
|
||||
)
|
||||
const expected = {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'CSV',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
],
|
||||
type: DataSourceType.Empty,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('can add a streaming data source', () => {
|
||||
const actual = dataLoadersReducer(
|
||||
{...INITIAL_STATE, type: DataSourceType.Streaming},
|
||||
addDataSource({
|
||||
name: 'CPU',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
})
|
||||
)
|
||||
const expected = {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'CPU',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
],
|
||||
type: DataSourceType.Streaming,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can remove a streaming data source', () => {
|
||||
const actual = dataLoadersReducer(
|
||||
{
|
||||
...INITIAL_STATE,
|
||||
type: DataSourceType.Streaming,
|
||||
dataSources: [
|
||||
{
|
||||
name: 'CPU',
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
removeDataSource('CPU')
|
||||
)
|
||||
const expected = {
|
||||
dataSources: [],
|
||||
type: DataSourceType.Streaming,
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,41 @@
|
|||
// Utils
|
||||
import {getInitialDataSources} from 'src/onboarding/utils/dataLoaders'
|
||||
|
||||
// Types
|
||||
import {Action} from 'src/onboarding/actions/dataLoaders'
|
||||
import {DataSource, DataSourceType} from 'src/types/v2/dataSources'
|
||||
|
||||
export interface DataLoadersState {
|
||||
dataSources: DataSource[]
|
||||
type: DataSourceType
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: DataLoadersState = {
|
||||
dataSources: [],
|
||||
type: DataSourceType.Empty,
|
||||
}
|
||||
|
||||
export default (state = INITIAL_STATE, action: Action): DataLoadersState => {
|
||||
switch (action.type) {
|
||||
case 'SET_DATA_LOADERS_TYPE':
|
||||
return {
|
||||
...state,
|
||||
type: action.payload.type,
|
||||
dataSources: getInitialDataSources(action.payload.type),
|
||||
}
|
||||
case 'ADD_DATA_SOURCE':
|
||||
return {
|
||||
...state,
|
||||
dataSources: [...state.dataSources, action.payload.dataSource],
|
||||
}
|
||||
case 'REMOVE_DATA_SOURCE':
|
||||
return {
|
||||
...state,
|
||||
dataSources: state.dataSources.filter(
|
||||
ds => ds.name !== action.payload.dataSource
|
||||
),
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Types
|
||||
import {Action} from 'src/onboarding/actions/dataSources'
|
||||
import {DataSource} from 'src/types/v2/dataSources'
|
||||
|
||||
export type DataSourcesState = DataSource[]
|
||||
|
||||
const INITIAL_STATE: DataSourcesState = []
|
||||
|
||||
export default (state = INITIAL_STATE, action: Action): DataSourcesState => {
|
||||
switch (action.type) {
|
||||
case 'ADD_DATA_SOURCE':
|
||||
return [...state, action.payload.dataSource]
|
||||
case 'REMOVE_DATA_SOURCE':
|
||||
return state.filter(ds => ds.name !== action.payload.dataSource)
|
||||
case 'SET_DATA_SOURCES':
|
||||
return action.payload.dataSources
|
||||
case 'SET_ACTIVE_DATA_SOURCE':
|
||||
return state.map(ds => {
|
||||
if (ds.name === action.payload.dataSource) {
|
||||
return {...ds, active: true}
|
||||
}
|
||||
return {...ds, active: false}
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -2,17 +2,17 @@
|
|||
import {combineReducers} from 'redux'
|
||||
|
||||
// Reducers
|
||||
import dataSourcesReducer, {
|
||||
DataSourcesState,
|
||||
} from 'src/onboarding/reducers/dataSources'
|
||||
import dataLoadersReducer, {
|
||||
DataLoadersState,
|
||||
} from 'src/onboarding/reducers/dataLoaders'
|
||||
import stepsReducer, {OnboardingStepsState} from 'src/onboarding/reducers/steps'
|
||||
|
||||
export interface OnboardingState {
|
||||
steps: OnboardingStepsState
|
||||
dataSources: DataSourcesState
|
||||
dataLoaders: DataLoadersState
|
||||
}
|
||||
|
||||
export default combineReducers<OnboardingState>({
|
||||
steps: stepsReducer,
|
||||
dataSources: dataSourcesReducer,
|
||||
dataLoaders: dataLoadersReducer,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
export const telegrafConfigID = '030358c935b18000'
|
||||
|
||||
export const telegrafConfig = {
|
||||
id: telegrafConfigID,
|
||||
name: 'in n out',
|
||||
created: '2018-11-28T18:56:48.854337-08:00',
|
||||
lastModified: '2018-11-28T18:56:48.854337-08:00',
|
||||
lastModifiedBy: '030358b695318000',
|
||||
agent: {collectionInterval: 15},
|
||||
plugins: [
|
||||
{name: 'cpu', type: 'input', comment: 'this is a test', config: {}},
|
||||
{
|
||||
name: 'influxdb_v2',
|
||||
type: 'output',
|
||||
comment: 'write to influxdb v2',
|
||||
config: {
|
||||
urls: ['http://127.0.0.1:9999'],
|
||||
token:
|
||||
'm4aUjEIhM758JzJgRmI6f3KNOBw4ZO77gdwERucF0bj4QOLHViD981UWzjaxW9AbyA5THOMBp2SVZqzbui2Ehw==',
|
||||
organization: 'default',
|
||||
bucket: 'defbuck',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const telegrafConfigsResponse = {
|
||||
data: {
|
||||
configurations: [telegrafConfig],
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
date: 'Thu, 29 Nov 2018 18:10:21 GMT',
|
||||
'content-length': '570',
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
config: {
|
||||
transformRequest: {},
|
||||
transformResponse: {},
|
||||
timeout: 0,
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||
maxContentLength: -1,
|
||||
headers: {Accept: 'application/json, text/plain, */*'},
|
||||
method: 'get',
|
||||
url: '/api/v2/telegrafs?org=',
|
||||
},
|
||||
request: {},
|
||||
}
|
||||
|
||||
export const token =
|
||||
'm4aUjEIhM758JzJgRmI6f3KNOBw4ZO77gdwERucF0bj4QOLHViD981UWzjaxW9AbyA5THOMBp2SVZqzbui2Ehw=='
|
||||
|
||||
export const authResponse = {
|
||||
data: {
|
||||
links: {self: '/api/v2/authorizations'},
|
||||
auths: [
|
||||
{
|
||||
links: {
|
||||
self: '/api/v2/authorizations/030358b6aa718000',
|
||||
user: '/api/v2/users/030358b695318000',
|
||||
},
|
||||
id: '030358b6aa718000',
|
||||
token,
|
||||
status: 'active',
|
||||
user: 'iris',
|
||||
userID: '030358b695318000',
|
||||
permissions: [
|
||||
{action: 'create', resource: 'user'},
|
||||
{action: 'delete', resource: 'user'},
|
||||
{action: 'write', resource: 'org'},
|
||||
{action: 'write', resource: 'bucket/030358b6aa318000'},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
date: 'Thu, 29 Nov 2018 18:10:21 GMT',
|
||||
'content-length': '522',
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
},
|
||||
config: {
|
||||
transformRequest: {},
|
||||
transformResponse: {},
|
||||
timeout: 0,
|
||||
xsrfCookieName: 'XSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-XSRF-TOKEN',
|
||||
maxContentLength: -1,
|
||||
headers: {Accept: 'application/json, text/plain, */*'},
|
||||
method: 'get',
|
||||
url: '/api/v2/authorizations?user=',
|
||||
},
|
||||
request: {},
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Types
|
||||
import {DataSourceType, ConfigurationState} from 'src/types/v2/dataSources'
|
||||
|
||||
export const getInitialDataSources = (type: DataSourceType) => {
|
||||
if (type === DataSourceType.Streaming) {
|
||||
return []
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: type,
|
||||
configured: ConfigurationState.Unconfigured,
|
||||
active: true,
|
||||
configs: null,
|
||||
},
|
||||
]
|
||||
}
|
|
@ -7,6 +7,13 @@ export enum ConfigurationState {
|
|||
Error = 'error',
|
||||
}
|
||||
|
||||
export enum DataSourceType {
|
||||
CSV = 'CSV',
|
||||
Streaming = 'Streaming',
|
||||
LineProtocol = 'Line Protocol',
|
||||
Empty = '',
|
||||
}
|
||||
|
||||
export interface DataSource {
|
||||
name: string
|
||||
configured: ConfigurationState
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
DashboardsApi,
|
||||
CellsApi,
|
||||
TelegrafsApi,
|
||||
AuthorizationsApi,
|
||||
} from 'src/api'
|
||||
|
||||
const basePath = '/api/v2'
|
||||
|
@ -12,4 +13,5 @@ export const taskAPI = new TasksApi({basePath})
|
|||
export const usersAPI = new UsersApi({basePath})
|
||||
export const dashboardsAPI = new DashboardsApi({basePath})
|
||||
export const cellsAPI = new CellsApi({basePath})
|
||||
export const telegrafsApi = new TelegrafsApi({basePath})
|
||||
export const telegrafsAPI = new TelegrafsApi({basePath})
|
||||
export const authorizationsAPI = new AuthorizationsApi({basePath})
|
||||
|
|
Loading…
Reference in New Issue