Merge pull request #1764 from influxdata/feat/onboarding-routing

Handling onboarding with react router
pull/10616/head
Brandon Farmer 2018-12-06 13:54:48 -08:00 committed by GitHub
commit 3b8578c7d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 215 additions and 111 deletions

View File

@ -1,6 +1,7 @@
// Libraries // Libraries
import React, {ReactElement, PureComponent} from 'react' import React, {ReactElement, PureComponent} from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {InjectedRouter} from 'react-router'
// APIs // APIs
import {getSetupStatus} from 'src/onboarding/apis' import {getSetupStatus} from 'src/onboarding/apis'
@ -10,8 +11,9 @@ import {notify as notifyAction} from 'src/shared/actions/notifications'
// Components // Components
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import OnboardingWizard from 'src/onboarding/containers/OnboardingWizard'
import Notifications from 'src/shared/components/notifications/Notifications' // Utils
import {isOnboardingURL} from 'src/onboarding/utils'
// Types // Types
import {Notification, NotificationFunc, RemoteDataState} from 'src/types' import {Notification, NotificationFunc, RemoteDataState} from 'src/types'
@ -24,6 +26,7 @@ interface State {
interface Props { interface Props {
links: Links links: Links
router: InjectedRouter
children: ReactElement<any> children: ReactElement<any>
notify: (message: Notification | NotificationFunc) => void notify: (message: Notification | NotificationFunc) => void
} }
@ -40,35 +43,35 @@ export class Setup extends PureComponent<Props, State> {
} }
public async componentDidMount() { public async componentDidMount() {
const {links} = this.props const {links, router} = this.props
if (isOnboardingURL()) {
this.setState({
loading: RemoteDataState.Done,
})
return
}
const isSetupAllowed = await getSetupStatus(links.setup) const isSetupAllowed = await getSetupStatus(links.setup)
this.setState({ this.setState({
loading: RemoteDataState.Done, loading: RemoteDataState.Done,
isSetupComplete: !isSetupAllowed,
}) })
if (!isSetupAllowed) {
return
}
router.push('/onboarding/0')
} }
public render() { public render() {
const {isSetupComplete} = this.state
if (this.isLoading) { if (this.isLoading) {
return <div className="page-spinner" /> return <div className="page-spinner" />
}
if (!isSetupComplete) {
return (
<div className="chronograf-root">
<Notifications inPresentationMode={true} />
<OnboardingWizard onCompleteSetup={this.handleCompleteSetup} />
</div>
)
} else { } else {
return this.props.children && React.cloneElement(this.props.children) return this.props.children && React.cloneElement(this.props.children)
} }
} }
public handleCompleteSetup = () => {
this.setState({isSetupComplete: true})
}
private get isLoading(): boolean { private get isLoading(): boolean {
const {loading} = this.state const {loading} = this.state
return ( return (

View File

@ -33,6 +33,8 @@ import GetLinks from 'src/shared/containers/GetLinks'
import GetMe from 'src/shared/containers/GetMe' import GetMe from 'src/shared/containers/GetMe'
import SourcesPage from 'src/sources/components/SourcesPage' import SourcesPage from 'src/sources/components/SourcesPage'
import OnboardingWizardPage from 'src/onboarding/containers/OnboardingWizardPage'
// Actions // Actions
import {disablePresentationMode} from 'src/shared/actions/app' import {disablePresentationMode} from 'src/shared/actions/app'
@ -77,6 +79,14 @@ class Root extends PureComponent {
<Router history={history}> <Router history={history}>
<Route component={GetLinks}> <Route component={GetLinks}>
<Route component={Setup}> <Route component={Setup}>
<Route
path="/onboarding/:stepID"
component={OnboardingWizardPage}
/>
<Route
path="/onboarding/:stepID/:substepID"
component={OnboardingWizardPage}
/>
<Route component={Signin}> <Route component={Signin}>
<Route component={GetMe}> <Route component={GetMe}>
<Route component={GetOrganizations}> <Route component={GetOrganizations}>
@ -116,8 +126,8 @@ class Root extends PureComponent {
</Route> </Route>
</Route> </Route>
</Route> </Route>
<Route path="*" component={NotFound} />
</Route> </Route>
<Route path="*" component={NotFound} />
</Router> </Router>
</Provider> </Provider>
) )

View File

@ -4,12 +4,7 @@ import {StepStatus} from 'src/clockface/constants/wizard'
// Types // Types
import {SetupParams} from 'src/onboarding/apis' import {SetupParams} from 'src/onboarding/apis'
export type Action = export type Action = SetSetupParams | SetStepStatus
| SetSetupParams
| IncrementCurrentStepIndex
| DecrementCurrentStepIndex
| SetCurrentStepIndex
| SetStepStatus
interface SetSetupParams { interface SetSetupParams {
type: 'SET_SETUP_PARAMS' type: 'SET_SETUP_PARAMS'
@ -21,32 +16,6 @@ export const setSetupParams = (setupParams: SetupParams): SetSetupParams => ({
payload: {setupParams}, payload: {setupParams},
}) })
interface SetCurrentStepIndex {
type: 'SET_CURRENT_STEP_INDEX'
payload: {index: number}
}
export const setCurrentStepIndex = (index: number): SetCurrentStepIndex => ({
type: 'SET_CURRENT_STEP_INDEX',
payload: {index},
})
interface IncrementCurrentStepIndex {
type: 'INCREMENT_CURRENT_STEP_INDEX'
}
export const incrementCurrentStepIndex = (): IncrementCurrentStepIndex => ({
type: 'INCREMENT_CURRENT_STEP_INDEX',
})
interface DecrementCurrentStepIndex {
type: 'DECREMENT_CURRENT_STEP_INDEX'
}
export const decrementCurrentStepIndex = (): DecrementCurrentStepIndex => ({
type: 'DECREMENT_CURRENT_STEP_INDEX',
})
interface SetStepStatus { interface SetStepStatus {
type: 'SET_STEP_STATUS' type: 'SET_STEP_STATUS'
payload: {index: number; status: StepStatus} payload: {index: number; status: StepStatus}

View File

@ -1,6 +1,7 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import _ from 'lodash' import _ from 'lodash'
import {withRouter, WithRouterProps} from 'react-router'
// Components // Components
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
@ -16,27 +17,44 @@ import ConfigureDataSourceSwitcher from 'src/onboarding/components/configureStep
import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard' import {OnboardingStepProps} from 'src/onboarding/containers/OnboardingWizard'
import {TelegrafPlugin, DataLoaderType} from 'src/types/v2/dataLoaders' import {TelegrafPlugin, DataLoaderType} from 'src/types/v2/dataLoaders'
export interface Props extends OnboardingStepProps { export interface OwnProps extends OnboardingStepProps {
telegrafPlugins: TelegrafPlugin[] telegrafPlugins: TelegrafPlugin[]
type: DataLoaderType type: DataLoaderType
} }
interface State { interface RouterProps {
currentDataSourceIndex: number params: {
stepID: string
substepID: string
}
} }
type Props = OwnProps & WithRouterProps & RouterProps
@ErrorHandling @ErrorHandling
class ConfigureDataSourceStep extends PureComponent<Props, State> { class ConfigureDataSourceStep extends PureComponent<Props> {
constructor(props: Props) { constructor(props: Props) {
super(props) super(props)
}
this.state = { public componentDidMount() {
currentDataSourceIndex: 0, const {
router,
params: {stepID, substepID},
} = this.props
if (substepID === undefined) {
router.replace(`/onboarding/${stepID}/0`)
} }
} }
public render() { public render() {
const {telegrafPlugins, type, setupParams} = this.props const {
telegrafPlugins,
type,
params: {substepID},
setupParams,
} = this.props
return ( return (
<div className="onboarding-step"> <div className="onboarding-step">
@ -44,8 +62,8 @@ class ConfigureDataSourceStep extends PureComponent<Props, State> {
bucket={_.get(setupParams, 'bucket', '')} bucket={_.get(setupParams, 'bucket', '')}
org={_.get(setupParams, 'org', '')} org={_.get(setupParams, 'org', '')}
telegrafPlugins={telegrafPlugins} telegrafPlugins={telegrafPlugins}
currentIndex={this.state.currentDataSourceIndex}
dataLoaderType={type} dataLoaderType={type}
currentIndex={+substepID}
/> />
<div className="wizard-button-bar"> <div className="wizard-button-bar">
<Button <Button
@ -68,26 +86,27 @@ class ConfigureDataSourceStep extends PureComponent<Props, State> {
} }
private handleNext = () => { private handleNext = () => {
const {onIncrementCurrentStepIndex, telegrafPlugins} = this.props const {
const {currentDataSourceIndex} = this.state onIncrementCurrentStepIndex,
telegrafPlugins,
params: {substepID, stepID},
router,
} = this.props
if (currentDataSourceIndex >= telegrafPlugins.length - 1) { const index = +substepID
if (index >= telegrafPlugins.length - 1) {
onIncrementCurrentStepIndex() onIncrementCurrentStepIndex()
} else { } else {
this.setState({currentDataSourceIndex: currentDataSourceIndex + 1}) router.push(`/onboarding/${stepID}/${index + 1}`)
} }
} }
private handlePrevious = () => { private handlePrevious = () => {
const {onDecrementCurrentStepIndex} = this.props const {router} = this.props
const {currentDataSourceIndex} = this.state
if (currentDataSourceIndex === 0) { router.goBack()
onDecrementCurrentStepIndex()
} else {
this.setState({currentDataSourceIndex: currentDataSourceIndex - 1})
}
} }
} }
export default ConfigureDataSourceStep export default withRouter<OwnProps>(ConfigureDataSourceStep)

View File

@ -1,5 +1,6 @@
// Libraries // Libraries
import React, {PureComponent} from 'react' import React, {PureComponent} from 'react'
import {withRouter, WithRouterProps} from 'react-router'
// Components // Components
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
@ -21,7 +22,7 @@ import {
TelegrafPluginName, TelegrafPluginName,
} from 'src/types/v2/dataLoaders' } from 'src/types/v2/dataLoaders'
export interface Props extends OnboardingStepProps { export interface OwnProps extends OnboardingStepProps {
bucket: string bucket: string
telegrafPlugins: TelegrafPlugin[] telegrafPlugins: TelegrafPlugin[]
type: DataLoaderType type: DataLoaderType
@ -30,6 +31,15 @@ export interface Props extends OnboardingStepProps {
onSetDataLoadersType: (type: DataLoaderType) => void onSetDataLoadersType: (type: DataLoaderType) => void
} }
interface RouterProps {
params: {
stepID: string
substepID: string
}
}
type Props = OwnProps & RouterProps & WithRouterProps
interface State { interface State {
showStreamingSources: boolean showStreamingSources: boolean
} }
@ -72,7 +82,7 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
private get title(): string { private get title(): string {
const {bucket} = this.props const {bucket} = this.props
if (this.state.showStreamingSources) { if (this.isStreaming) {
return `Select Streaming Data Sources to add to ${bucket || return `Select Streaming Data Sources to add to ${bucket ||
'your bucket'}` 'your bucket'}`
} }
@ -80,10 +90,7 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
} }
private get selector(): JSX.Element { private get selector(): JSX.Element {
if ( if (this.props.type === DataLoaderType.Streaming && this.isStreaming) {
this.props.type === DataLoaderType.Streaming &&
this.state.showStreamingSources
) {
return ( return (
<StreamingDataSourceSelector <StreamingDataSourceSelector
telegrafPlugins={this.props.telegrafPlugins} telegrafPlugins={this.props.telegrafPlugins}
@ -100,11 +107,13 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
} }
private handleClickNext = () => { private handleClickNext = () => {
if ( const {
this.props.type === DataLoaderType.Streaming && router,
!this.state.showStreamingSources params: {stepID},
) { } = this.props
this.setState({showStreamingSources: true})
if (this.props.type === DataLoaderType.Streaming && !this.isStreaming) {
router.push(`/onboarding/${stepID}/streaming`)
return return
} }
@ -112,11 +121,6 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
} }
private handleClickBack = () => { private handleClickBack = () => {
if (this.props.type === DataLoaderType.Streaming) {
this.setState({showStreamingSources: false})
return
}
this.props.onDecrementCurrentStepIndex() this.props.onDecrementCurrentStepIndex()
} }
@ -145,6 +149,10 @@ class SelectDataSourceStep extends PureComponent<Props, State> {
} }
this.props.onAddTelegrafPlugin(plugin) this.props.onAddTelegrafPlugin(plugin)
} }
private get isStreaming(): boolean {
return this.props.params.substepID === 'streaming'
}
} }
export default SelectDataSourceStep export default withRouter<OwnProps>(SelectDataSourceStep)

View File

@ -16,13 +16,8 @@ import OnboardingStepSwitcher from 'src/onboarding/components/OnboardingStepSwit
// Actions // Actions
import {notify as notifyAction} from 'src/shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import { import {setSetupParams, setStepStatus} from 'src/onboarding/actions/steps'
setSetupParams,
incrementCurrentStepIndex,
decrementCurrentStepIndex,
setCurrentStepIndex,
setStepStatus,
} from 'src/onboarding/actions/steps'
import { import {
setDataLoadersType, setDataLoadersType,
addTelegrafPlugin, addTelegrafPlugin,
@ -62,14 +57,15 @@ interface OwnProps {
startStep?: number startStep?: number
stepStatuses?: StepStatus[] stepStatuses?: StepStatus[]
onCompleteSetup: () => void onCompleteSetup: () => void
currentStepIndex: number
onIncrementCurrentStepIndex: () => void
onDecrementCurrentStepIndex: () => void
onSetCurrentStepIndex: (stepNumber: number) => void
} }
interface DispatchProps { interface DispatchProps {
notify: (message: Notification | NotificationFunc) => void notify: (message: Notification | NotificationFunc) => void
onSetSetupParams: typeof setSetupParams onSetSetupParams: typeof setSetupParams
onIncrementCurrentStepIndex: typeof incrementCurrentStepIndex
onDecrementCurrentStepIndex: typeof decrementCurrentStepIndex
onSetCurrentStepIndex: typeof setCurrentStepIndex
onSetStepStatus: typeof setStepStatus onSetStepStatus: typeof setStepStatus
onSetDataLoadersType: typeof setDataLoadersType onSetDataLoadersType: typeof setDataLoadersType
onAddTelegrafPlugin: typeof addTelegrafPlugin onAddTelegrafPlugin: typeof addTelegrafPlugin
@ -84,7 +80,6 @@ interface DataLoadersProps {
interface StateProps { interface StateProps {
links: Links links: Links
currentStepIndex: number
stepStatuses: StepStatus[] stepStatuses: StepStatus[]
setupParams: SetupParams setupParams: SetupParams
dataLoaders: DataLoadersProps dataLoaders: DataLoadersProps
@ -238,12 +233,11 @@ class OnboardingWizard extends PureComponent<Props> {
const mstp = ({ const mstp = ({
links, links,
onboarding: { onboarding: {
steps: {currentStepIndex, stepStatuses, setupParams}, steps: {stepStatuses, setupParams},
dataLoaders, dataLoaders,
}, },
}: AppState): StateProps => ({ }: AppState): StateProps => ({
links, links,
currentStepIndex,
stepStatuses, stepStatuses,
setupParams, setupParams,
dataLoaders, dataLoaders,
@ -252,9 +246,6 @@ const mstp = ({
const mdtp: DispatchProps = { const mdtp: DispatchProps = {
notify: notifyAction, notify: notifyAction,
onSetSetupParams: setSetupParams, onSetSetupParams: setSetupParams,
onDecrementCurrentStepIndex: decrementCurrentStepIndex,
onIncrementCurrentStepIndex: incrementCurrentStepIndex,
onSetCurrentStepIndex: setCurrentStepIndex,
onSetStepStatus: setStepStatus, onSetStepStatus: setStepStatus,
onSetDataLoadersType: setDataLoadersType, onSetDataLoadersType: setDataLoadersType,
onAddTelegrafPlugin: addTelegrafPlugin, onAddTelegrafPlugin: addTelegrafPlugin,

View File

@ -0,0 +1,109 @@
// Libraries
import React, {ReactElement, PureComponent} from 'react'
import {connect} from 'react-redux'
import {withRouter, WithRouterProps} from 'react-router'
// Actions
import {notify as notifyAction} from 'src/shared/actions/notifications'
// Components
import {ErrorHandling} from 'src/shared/decorators/errors'
import OnboardingWizard from 'src/onboarding/containers/OnboardingWizard'
import Notifications from 'src/shared/components/notifications/Notifications'
// Types
import {Notification, NotificationFunc, RemoteDataState} from 'src/types'
import {Links} from 'src/types/v2/links'
interface State {
loading: RemoteDataState
isSetupComplete: boolean
}
interface PassedProps {
children: ReactElement<any>
params: {
stepID: string
}
}
interface ConnectedStateProps {
links: Links
}
interface ConnectedDispatchProps {
notify: (message: Notification | NotificationFunc) => void
}
type Props = PassedProps &
WithRouterProps &
ConnectedStateProps &
ConnectedDispatchProps
@ErrorHandling
export class OnboardingWizardPage extends PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
loading: RemoteDataState.NotStarted,
isSetupComplete: false,
}
}
public render() {
const {params} = this.props
return (
<div className="chronograf-root">
<Notifications inPresentationMode={true} />
<OnboardingWizard
onDecrementCurrentStepIndex={this.handleDecrementStepIndex}
onIncrementCurrentStepIndex={this.handleIncrementStepIndex}
onSetCurrentStepIndex={this.setStepIndex}
currentStepIndex={+params.stepID}
onCompleteSetup={this.handleCompleteSetup}
/>
</div>
)
}
public handleCompleteSetup = () => {
this.setState({isSetupComplete: true})
}
private handleDecrementStepIndex = () => {
const {router} = this.props
router.goBack()
}
private handleIncrementStepIndex = () => {
const {
params: {stepID},
} = this.props
this.setStepIndex(+stepID + 1)
}
private setStepIndex = (index: number) => {
const {router} = this.props
router.push(`/onboarding/${index}`)
}
}
const mstp = ({links}) => ({links})
const mdtp = {
notify: notifyAction,
}
export default connect<
ConnectedStateProps,
ConnectedDispatchProps,
PassedProps
>(
mstp,
mdtp
)(withRouter<Props>(OnboardingWizardPage))

View File

@ -6,13 +6,11 @@ import {Action} from 'src/onboarding/actions/steps'
import {SetupParams} from 'src/onboarding/apis' import {SetupParams} from 'src/onboarding/apis'
export interface OnboardingStepsState { export interface OnboardingStepsState {
currentStepIndex: number
stepStatuses: StepStatus[] stepStatuses: StepStatus[]
setupParams: SetupParams setupParams: SetupParams
} }
const INITIAL_STATE: OnboardingStepsState = { const INITIAL_STATE: OnboardingStepsState = {
currentStepIndex: 0,
stepStatuses: new Array(6).fill(StepStatus.Incomplete), stepStatuses: new Array(6).fill(StepStatus.Incomplete),
setupParams: null, setupParams: null,
} }
@ -24,12 +22,6 @@ export default (
switch (action.type) { switch (action.type) {
case 'SET_SETUP_PARAMS': case 'SET_SETUP_PARAMS':
return {...state, setupParams: action.payload.setupParams} return {...state, setupParams: action.payload.setupParams}
case 'INCREMENT_CURRENT_STEP_INDEX':
return {...state, currentStepIndex: state.currentStepIndex + 1}
case 'DECREMENT_CURRENT_STEP_INDEX':
return {...state, currentStepIndex: state.currentStepIndex - 1}
case 'SET_CURRENT_STEP_INDEX':
return {...state, currentStepIndex: action.payload.index}
case 'SET_STEP_STATUS': case 'SET_STEP_STATUS':
const stepStatuses = [...state.stepStatuses] const stepStatuses = [...state.stepStatuses]
stepStatuses[action.payload.index] = action.payload.status stepStatuses[action.payload.index] = action.payload.status

View File

@ -0,0 +1,3 @@
export const isOnboardingURL = () => {
return !!window.location.pathname.match(/\/onboarding/)
}