Source form and page

pull/10616/head
Andrew Watkins 2018-06-15 17:22:21 -07:00
parent 36355f18d9
commit 08bf9bcd27
9 changed files with 465 additions and 423 deletions

View File

@ -450,8 +450,7 @@ export const populateNamespacesAsync = (
export const getSourceAndPopulateNamespacesAsync = (sourceID: string) => async ( export const getSourceAndPopulateNamespacesAsync = (sourceID: string) => async (
dispatch dispatch
): Promise<void> => { ): Promise<void> => {
const response = await getSource(sourceID) const source = await getSource(sourceID)
const source = response.data
const proxyLink = getDeep<string | null>(source, 'links.proxy', null) const proxyLink = getDeep<string | null>(source, 'links.proxy', null)

View File

@ -9,29 +9,51 @@ export function getSources() {
}) })
} }
export function getSource(id) { export const getSource = async (id: string): Promise<Source> => {
return AJAX({ try {
const {data: source} = await AJAX({
url: null, url: null,
resource: 'sources', resource: 'sources',
id, id,
}) })
return source
} catch (error) {
throw error
}
} }
export function createSource(attributes) { export const createSource = async (
return AJAX({ attributes: Partial<Source>
): Promise<Source> => {
try {
const {data: source} = await AJAX({
url: null, url: null,
resource: 'sources', resource: 'sources',
method: 'POST', method: 'POST',
data: attributes, data: attributes,
}) })
return source
} catch (error) {
throw error
}
} }
export function updateSource(newSource) { export const updateSource = async (
return AJAX({ newSource: Partial<Source>
): Promise<Source> => {
try {
const {data: source} = await AJAX({
url: newSource.links.self, url: newSource.links.self,
method: 'PATCH', method: 'PATCH',
data: newSource, data: newSource,
}) })
return source
} catch (error) {
throw error
}
} }
export function deleteSource(source) { export function deleteSource(source) {

View File

@ -155,8 +155,10 @@ export const notifyUnableToRetrieveSources = () => 'Unable to retrieve sources.'
export const notifyUnableToConnectSource = sourceName => export const notifyUnableToConnectSource = sourceName =>
`Unable to connect to source ${sourceName}.` `Unable to connect to source ${sourceName}.`
export const notifyErrorConnectingToSource = errorMessage => export const notifyErrorConnectingToSource = errorMessage => ({
`Unable to connect to InfluxDB source: ${errorMessage}` ...defaultErrorNotification,
message: `Unable to connect to InfluxDB source: ${errorMessage}`,
})
// Multitenancy User Notifications // Multitenancy User Notifications
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -1,208 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {connect} from 'react-redux'
import _ from 'lodash'
import {insecureSkipVerifyText} from 'shared/copy/tooltipText'
import {SUPERADMIN_ROLE} from 'src/auth/Authorized'
export const SourceForm = ({
source,
editMode,
onSubmit,
onInputChange,
onBlurSourceURL,
isUsingAuth,
gotoPurgatory,
isInitialSource,
me,
}) => (
<div className="panel-body">
{isUsingAuth && isInitialSource ? (
<div className="text-center">
{me.role === SUPERADMIN_ROLE ? (
<h3>
<strong>{me.currentOrganization.name}</strong> has no connections
</h3>
) : (
<h3>
<strong>{me.currentOrganization.name}</strong> has no connections
available to <em>{me.role}s</em>
</h3>
)}
<h6>Add a Connection below:</h6>
</div>
) : null}
<form onSubmit={onSubmit}>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="connect-string">Connection String</label>
<input
type="text"
name="url"
className="form-control"
id="connect-string"
placeholder="Address of InfluxDB"
onChange={onInputChange}
value={source.url}
onBlur={onBlurSourceURL}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
className="form-control"
id="name"
placeholder="Name this source"
onChange={onInputChange}
value={source.name}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
className="form-control"
id="username"
onChange={onInputChange}
value={source.username}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
className="form-control"
id="password"
onChange={onInputChange}
value={source.password}
/>
</div>
{_.get(source, 'type', '').includes('enterprise') ? (
<div className="form-group col-xs-12">
<label htmlFor="meta-url">Meta Service Connection URL</label>
<input
type="text"
name="metaUrl"
className="form-control"
id="meta-url"
placeholder="http://localhost:8091"
onChange={onInputChange}
value={source.metaUrl}
/>
</div>
) : null}
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="telegraf">Telegraf Database</label>
<input
type="text"
name="telegraf"
className="form-control"
id="telegraf"
onChange={onInputChange}
value={source.telegraf}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="defaultRP">Default Retention Policy</label>
<input
type="text"
name="defaultRP"
className="form-control"
id="defaultRP"
onChange={onInputChange}
value={source.defaultRP}
/>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="defaultConnectionCheckbox"
name="default"
checked={source.default}
onChange={onInputChange}
/>
<label htmlFor="defaultConnectionCheckbox">
Make this the default connection
</label>
</div>
</div>
{_.get(source, 'url', '').startsWith('https') ? (
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
name="insecureSkipVerify"
checked={source.insecureSkipVerify}
onChange={onInputChange}
/>
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div>
) : null}
<div className="form-group form-group-submit text-center col-xs-12 col-sm-6 col-sm-offset-3">
<button
className={classnames('btn btn-block', {
'btn-primary': editMode,
'btn-success': !editMode,
})}
type="submit"
>
<span className={`icon ${editMode ? 'checkmark' : 'plus'}`} />
{editMode ? 'Save Changes' : 'Add Connection'}
</button>
<br />
{isUsingAuth ? (
<button className="btn btn-link btn-sm" onClick={gotoPurgatory}>
<span className="icon shuffle" /> Switch Orgs
</button>
) : null}
</div>
</form>
</div>
)
const {bool, func, shape, string} = PropTypes
SourceForm.propTypes = {
source: shape({
url: string.isRequired,
name: string.isRequired,
username: string.isRequired,
password: string.isRequired,
telegraf: string.isRequired,
insecureSkipVerify: bool.isRequired,
default: bool.isRequired,
metaUrl: string.isRequired,
}).isRequired,
editMode: bool.isRequired,
onInputChange: func.isRequired,
onSubmit: func.isRequired,
onBlurSourceURL: func.isRequired,
me: shape({
role: string,
currentOrganization: shape({
id: string.isRequired,
name: string.isRequired,
}),
}),
isUsingAuth: bool,
isInitialSource: bool,
gotoPurgatory: func,
}
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
export default connect(mapStateToProps)(SourceForm)

View File

@ -0,0 +1,224 @@
import React, {PureComponent, FocusEvent, MouseEvent, ChangeEvent} from 'react'
import classnames from 'classnames'
import {connect} from 'react-redux'
import _ from 'lodash'
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
import {SUPERADMIN_ROLE} from 'src/auth/Authorized'
import {Source, Me} from 'src/types'
interface Props {
me: Me
source: Partial<Source>
editMode: boolean
isUsingAuth: boolean
gotoPurgatory: () => void
isInitialSource: boolean
onSubmit: (e: MouseEvent<HTMLFormElement>) => void
onInputChange: (e: ChangeEvent<HTMLInputElement>) => void
onBlurSourceURL: (e: FocusEvent<HTMLInputElement>) => void
}
export class SourceForm extends PureComponent<Props> {
public render() {
const {
source,
onSubmit,
isUsingAuth,
onInputChange,
gotoPurgatory,
onBlurSourceURL,
isInitialSource,
} = this.props
return (
<div className="panel-body">
{isUsingAuth && isInitialSource && this.authIndicatior}
<form onSubmit={onSubmit}>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="connect-string">Connection String</label>
<input
type="text"
name="url"
className="form-control"
id="connect-string"
placeholder="Address of InfluxDB"
onChange={onInputChange}
value={source.url}
onBlur={onBlurSourceURL}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="name">Name</label>
<input
type="text"
name="name"
className="form-control"
id="name"
placeholder="Name this source"
onChange={onInputChange}
value={source.name}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
className="form-control"
id="username"
onChange={onInputChange}
value={source.username}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
className="form-control"
id="password"
onChange={onInputChange}
value={source.password}
/>
</div>
{this.isEnterprise && (
<div className="form-group col-xs-12">
<label htmlFor="meta-url">Meta Service Connection URL</label>
<input
type="text"
name="metaUrl"
className="form-control"
id="meta-url"
placeholder="http://localhost:8091"
onChange={onInputChange}
value={source.metaUrl}
/>
</div>
)}
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="telegraf">Telegraf Database</label>
<input
type="text"
name="telegraf"
className="form-control"
id="telegraf"
onChange={onInputChange}
value={source.telegraf}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="defaultRP">Default Retention Policy</label>
<input
type="text"
name="defaultRP"
className="form-control"
id="defaultRP"
onChange={onInputChange}
value={source.defaultRP}
/>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="defaultConnectionCheckbox"
name="default"
checked={source.default}
onChange={onInputChange}
/>
<label htmlFor="defaultConnectionCheckbox">
Make this the default connection
</label>
</div>
</div>
{this.isHTTPS && (
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
name="insecureSkipVerify"
checked={source.insecureSkipVerify}
onChange={onInputChange}
/>
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div>
)}
<div className="form-group form-group-submit text-center col-xs-12 col-sm-6 col-sm-offset-3">
<button className={this.submitClass} type="submit">
<span className={this.submitIconClass} />
{this.submitText}
</button>
<br />
{isUsingAuth && (
<button className="btn btn-link btn-sm" onClick={gotoPurgatory}>
<span className="icon shuffle" /> Switch Orgs
</button>
)}
</div>
</form>
</div>
)
}
private get authIndicatior(): JSX.Element {
const {me} = this.props
return (
<div className="text-center">
{me.role.name === SUPERADMIN_ROLE ? (
<h3>
<strong>{me.currentOrganization.name}</strong> has no connections
</h3>
) : (
<h3>
<strong>{me.currentOrganization.name}</strong> has no connections
available to <em>{me.role}s</em>
</h3>
)}
<h6>Add a Connection below:</h6>
</div>
)
}
private get submitText(): string {
const {editMode} = this.props
if (editMode) {
return 'Save Changes'
}
return 'Add Connection'
}
private get submitIconClass(): string {
const {editMode} = this.props
return `icon ${editMode ? 'checkmark' : 'plus'}`
}
private get submitClass(): string {
const {editMode} = this.props
return classnames('btn btn-block', {
'btn-primary': editMode,
'btn-success': !editMode,
})
}
private get isEnterprise(): boolean {
const {source} = this.props
return _.get(source, 'type', '').includes('enterprise')
}
private get isHTTPS(): boolean {
const {source} = this.props
return _.get(source, 'url', '').startsWith('https')
}
}
const mapStateToProps = ({auth: {isUsingAuth, me}}) => ({isUsingAuth, me})
export default connect(mapStateToProps)(SourceForm)

View File

@ -1,47 +1,67 @@
import React, {Component} from 'react' import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
import PropTypes from 'prop-types' import {withRouter, WithRouterProps} from 'react-router'
import {withRouter} from 'react-router'
import _ from 'lodash' import _ from 'lodash'
import {getSource} from 'shared/apis' import {getSource} from 'src/shared/apis'
import {createSource, updateSource} from 'shared/apis' import {createSource, updateSource} from 'src/shared/apis'
import { import {
addSource as addSourceAction, addSource as addSourceAction,
updateSource as updateSourceAction, updateSource as updateSourceAction,
} from 'shared/actions/sources' AddSource,
import {notify as notifyAction} from 'shared/actions/notifications' UpdateSource,
} from 'src/shared/actions/sources'
import {
notify as notifyAction,
PubishNotification,
} from 'src/shared/actions/notifications'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import Notifications from 'shared/components/Notifications' import Notifications from 'src/shared/components/Notifications'
import SourceForm from 'src/sources/components/SourceForm' import SourceForm from 'src/sources/components/SourceForm'
import FancyScrollbar from 'shared/components/FancyScrollbar' import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import SourceIndicator from 'shared/components/SourceIndicator' import SourceIndicator from 'src/shared/components/SourceIndicator'
import {DEFAULT_SOURCE} from 'shared/constants' import {DEFAULT_SOURCE} from 'src/shared/constants'
const initialPath = '/sources/new' import {Source} from 'src/types'
const INITIAL_PATH = '/sources/new'
import { import {
notifyErrorConnectingToSource,
notifySourceCreationSucceeded,
notifySourceCreationFailed,
notifySourceUdpated, notifySourceUdpated,
notifySourceUdpateFailed, notifySourceUdpateFailed,
} from 'shared/copy/notifications' notifySourceCreationFailed,
notifyErrorConnectingToSource,
notifySourceCreationSucceeded,
} from 'src/shared/copy/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props extends WithRouterProps {
notify: PubishNotification
addSource: AddSource
updateSource: UpdateSource
}
interface State {
isCreated: boolean
isLoading: boolean
source: Partial<Source>
editMode: boolean
isInitialSource: boolean
}
@ErrorHandling @ErrorHandling
class SourcePage extends Component { class SourcePage extends PureComponent<Props, State> {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
isLoading: true, isLoading: true,
isCreated: false,
source: DEFAULT_SOURCE, source: DEFAULT_SOURCE,
editMode: props.params.id !== undefined, editMode: props.params.id !== undefined,
isInitialSource: props.router.location.pathname === initialPath, isInitialSource: props.router.location.pathname === INITIAL_PATH,
} }
} }
componentDidMount() { public async componentDidMount() {
const {editMode} = this.state const {editMode} = this.state
const {params, notify} = this.props const {params, notify} = this.props
@ -49,156 +69,19 @@ class SourcePage extends Component {
return this.setState({isLoading: false}) return this.setState({isLoading: false})
} }
getSource(params.id) try {
.then(({data: source}) => { const source = await getSource(params.id)
this.setState({ this.setState({
source: {...DEFAULT_SOURCE, ...source}, source: {...DEFAULT_SOURCE, ...source},
isLoading: false, isLoading: false,
}) })
}) } catch (error) {
.catch(error => { notify(notifyErrorConnectingToSource(this.parseError(error)))
notify(notifyErrorConnectingToSource(this._parseError(error)))
this.setState({isLoading: false}) this.setState({isLoading: false})
}) }
} }
handleInputChange = e => { public render() {
let val = e.target.value
const name = e.target.name
if (e.target.type === 'checkbox') {
val = e.target.checked
}
this.setState(prevState => {
const source = {
...prevState.source,
[name]: val,
}
return {...prevState, source}
})
}
handleBlurSourceURL = () => {
const {source, editMode} = this.state
if (editMode) {
this.setState(this._normalizeSource)
return
}
if (!source.url) {
return
}
this.setState(this._normalizeSource, this._createSourceOnBlur)
}
handleSubmit = e => {
e.preventDefault()
const {isCreated, editMode} = this.state
const isNewSource = !editMode
if (!isCreated && isNewSource) {
return this.setState(this._normalizeSource, this._createSource)
}
this.setState(this._normalizeSource, this._updateSource)
}
gotoPurgatory = () => {
const {router} = this.props
router.push('/purgatory')
}
_normalizeSource({source}) {
const url = source.url.trim()
if (source.url.startsWith('http')) {
return {source: {...source, url}}
}
return {source: {...source, url: `http://${url}`}}
}
_createSourceOnBlur = () => {
const {source} = this.state
// if there is a type on source it has already been created
if (source.type) {
return
}
createSource(source)
.then(({data: sourceFromServer}) => {
this.props.addSource(sourceFromServer)
this.setState({
source: {...DEFAULT_SOURCE, ...sourceFromServer},
isCreated: true,
})
})
.catch(err => {
// dont want to flash this until they submit
const error = this._parseError(err)
console.error('Error creating InfluxDB connection: ', error)
})
}
_createSource = () => {
const {source} = this.state
const {notify} = this.props
createSource(source)
.then(({data: sourceFromServer}) => {
this.props.addSource(sourceFromServer)
this._redirect(sourceFromServer)
notify(notifySourceCreationSucceeded(source.name))
})
.catch(error => {
notify(notifySourceCreationFailed(source.name, this._parseError(error)))
})
}
_updateSource = () => {
const {source} = this.state
const {notify} = this.props
updateSource(source)
.then(({data: sourceFromServer}) => {
this.props.updateSource(sourceFromServer)
this._redirect(sourceFromServer)
notify(notifySourceUdpated(source.name))
})
.catch(error => {
notify(notifySourceUdpateFailed(source.name, this._parseError(error)))
})
}
_redirect = source => {
const {isInitialSource} = this.state
const {params, router} = this.props
if (isInitialSource) {
return this._redirectToApp(source)
}
router.push(`/sources/${params.sourceID}/manage-sources`)
}
_redirectToApp = source => {
const {location, router} = this.props
const {redirectPath} = location.query
if (!redirectPath) {
return router.push(`/sources/${source.id}/hosts`)
}
const fixedPath = redirectPath.replace(
/\/sources\/[^/]*/,
`/sources/${source.id}`
)
return router.push(fixedPath)
}
_parseError = error => {
return _.get(error, ['data', 'message'], error)
}
render() {
const {isLoading, source, editMode, isInitialSource} = this.state const {isLoading, source, editMode, isInitialSource} = this.state
if (isLoading) { if (isLoading) {
@ -248,31 +131,147 @@ class SourcePage extends Component {
</div> </div>
) )
} }
private handleSubmit = (e: MouseEvent<HTMLFormElement>): void => {
e.preventDefault()
const {isCreated, editMode} = this.state
const isNewSource = !editMode
if (!isCreated && isNewSource) {
return this.setState(this.normalizeSource, this.createSource)
}
this.setState(this.normalizeSource, this.updateSource)
}
private gotoPurgatory = (): void => {
const {router} = this.props
router.push('/purgatory')
}
private normalizeSource({source}) {
const url = source.url.trim()
if (source.url.startsWith('http')) {
return {source: {...source, url}}
}
return {source: {...source, url: `http://${url}`}}
}
private createSourceOnBlur = async () => {
const {source} = this.state
// if there is a type on source it has already been created
if (source.type) {
return
}
try {
const sourceFromServer = await createSource(source)
this.props.addSource(sourceFromServer)
this.setState({
source: {...DEFAULT_SOURCE, ...sourceFromServer},
isCreated: true,
})
} catch (err) {
// dont want to flash this until they submit
const error = this.parseError(err)
console.error('Error creating InfluxDB connection: ', error)
}
}
private createSource = async () => {
const {source} = this.state
const {notify} = this.props
try {
const sourceFromServer = await createSource(source)
this.props.addSource(sourceFromServer)
this.redirect(sourceFromServer)
notify(notifySourceCreationSucceeded(source.name))
} catch (err) {
// dont want to flash this until they submit
notify(notifySourceCreationFailed(source.name, this.parseError(err)))
}
}
private updateSource = async () => {
const {source} = this.state
const {notify} = this.props
try {
const sourceFromServer = await updateSource(source)
this.props.updateSource(sourceFromServer)
this.redirect(sourceFromServer)
notify(notifySourceUdpated(source.name))
} catch (error) {
notify(notifySourceUdpateFailed(source.name, this.parseError(error)))
}
}
private redirect = source => {
const {isInitialSource} = this.state
const {params, router} = this.props
if (isInitialSource) {
return this.redirectToApp(source)
}
router.push(`/sources/${params.sourceID}/manage-sources`)
}
private parseError = (error): string => {
return _.get(error, ['data', 'message'], error)
}
private redirectToApp = source => {
const {location, router} = this.props
const {redirectPath} = location.query
if (!redirectPath) {
return router.push(`/sources/${source.id}/hosts`)
}
const fixedPath = redirectPath.replace(
/\/sources\/[^/]*/,
`/sources/${source.id}`
)
return router.push(fixedPath)
}
private handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
let val = e.target.value
const name = e.target.name
if (e.target.type === 'checkbox') {
val = e.target.checked as any
}
this.setState(prevState => {
const source = {
...prevState.source,
[name]: val,
}
return {...prevState, source}
})
}
private handleBlurSourceURL = () => {
const {source, editMode} = this.state
if (editMode) {
this.setState(this.normalizeSource)
return
}
if (!source.url) {
return
}
this.setState(this.normalizeSource, this.createSourceOnBlur)
}
} }
const {func, shape, string} = PropTypes const mdtp = {
notify: notifyAction,
SourcePage.propTypes = { addSource: addSourceAction,
params: shape({ updateSource: updateSourceAction,
id: string,
sourceID: string,
}),
router: shape({
push: func.isRequired,
}).isRequired,
location: shape({
query: shape({
redirectPath: string,
}).isRequired,
}).isRequired,
notify: func.isRequired,
addSource: func.isRequired,
updateSource: func.isRequired,
} }
const mapDispatchToProps = dispatch => ({ export default connect(null, mdtp)(withRouter(SourcePage))
notify: bindActionCreators(notifyAction, dispatch),
addSource: bindActionCreators(addSourceAction, dispatch),
updateSource: bindActionCreators(updateSourceAction, dispatch),
})
export default connect(null, mapDispatchToProps)(withRouter(SourcePage))

View File

@ -25,7 +25,7 @@ import {
TagValues, TagValues,
} from './query' } from './query'
import {AlertRule, Kapacitor, Task, RuleValues} from './kapacitor' import {AlertRule, Kapacitor, Task, RuleValues} from './kapacitor'
import {Source, SourceLinks} from './sources' import {NewSource, Source, SourceLinks} from './sources'
import {DropdownAction, DropdownItem, Constructable} from './shared' import {DropdownAction, DropdownItem, Constructable} from './shared'
import { import {
Notification, Notification,
@ -70,6 +70,7 @@ export {
TagValues, TagValues,
AlertRule, AlertRule,
Kapacitor, Kapacitor,
NewSource,
Source, Source,
SourceLinks, SourceLinks,
DropdownAction, DropdownAction,

View File

@ -1,5 +1,7 @@
import {Kapacitor, Service} from './' import {Kapacitor, Service} from './'
export type NewSource = Pick<Source, Exclude<keyof Source, 'id'>>
export interface Source { export interface Source {
id: string id: string
name: string name: string

View File

@ -2,6 +2,7 @@ import React from 'react'
import {shallow} from 'enzyme' import {shallow} from 'enzyme'
import {SourceForm} from 'src/sources/components/SourceForm' import {SourceForm} from 'src/sources/components/SourceForm'
import {me} from 'test/resources'
const setup = (override = {}) => { const setup = (override = {}) => {
const noop = () => {} const noop = () => {}
@ -23,7 +24,7 @@ const setup = (override = {}) => {
isUsingAuth: false, isUsingAuth: false,
gotoPurgatory: noop, gotoPurgatory: noop,
isInitialSource: false, isInitialSource: false,
me: {}, me,
...override, ...override,
} }