feat(ui): Add the ability to name a scraper target

pull/11809/head
Iris Scholten 2019-02-11 16:04:37 -08:00
parent 0213db306c
commit e04e721bf5
14 changed files with 420 additions and 97 deletions

View File

@ -1,6 +1,7 @@
## v2.0.0-alpha.3 [unreleased] ## v2.0.0-alpha.3 [unreleased]
### Features ### Features
1. [11809](https://github.com/influxdata/influxdb/pull/11809): Add the ability to name a scraper target
### Bug Fixes ### Bug Fixes

6
ui/package-lock.json generated
View File

@ -829,9 +829,9 @@
"dev": true "dev": true
}, },
"@influxdata/influx": { "@influxdata/influx": {
"version": "0.2.3", "version": "0.2.5",
"resolved": "https://registry.npmjs.org/@influxdata/influx/-/influx-0.2.3.tgz", "resolved": "https://registry.npmjs.org/@influxdata/influx/-/influx-0.2.5.tgz",
"integrity": "sha512-XZtvebkRwurLnXVkf5+M29uQqq67ZivXKCgY7gvT1nZKres7hvPRSgFGyNeXnZf680orH/9diWp6WrzLDPdhkg==", "integrity": "sha512-K5arbttAaGIC3iuW5OcJxmy8+qtLRD9pfIoS7NtbN7PUL+NDIzIK7tMrUcvAIPPBREDc4W/lNuicoy79ukMhkw==",
"requires": { "requires": {
"axios": "^0.18.0" "axios": "^0.18.0"
} }

View File

@ -117,7 +117,7 @@
}, },
"dependencies": { "dependencies": {
"@influxdata/react-custom-scrollbars": "4.3.8", "@influxdata/react-custom-scrollbars": "4.3.8",
"@influxdata/influx": "^0.2.3", "@influxdata/influx": "^0.2.5",
"axios": "^0.18.0", "axios": "^0.18.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bignumber.js": "^4.0.2", "bignumber.js": "^4.0.2",

View File

@ -57,6 +57,7 @@ export type Action =
| SetConfigArrayValue | SetConfigArrayValue
| SetScraperTargetBucket | SetScraperTargetBucket
| SetScraperTargetURL | SetScraperTargetURL
| SetScraperTargetName
| SetScraperTargetID | SetScraperTargetID
| ClearDataLoaders | ClearDataLoaders
| SetTelegrafConfigName | SetTelegrafConfigName
@ -246,6 +247,16 @@ export const setScraperTargetURL = (url: string): SetScraperTargetURL => ({
payload: {url}, payload: {url},
}) })
interface SetScraperTargetName {
type: 'SET_SCRAPER_TARGET_NAME'
payload: {name: string}
}
export const setScraperTargetName = (name: string): SetScraperTargetName => ({
type: 'SET_SCRAPER_TARGET_NAME',
payload: {name},
})
interface SetScraperTargetID { interface SetScraperTargetID {
type: 'SET_SCRAPER_TARGET_ID' type: 'SET_SCRAPER_TARGET_ID'
payload: {id: string} payload: {id: string}
@ -426,7 +437,7 @@ export const saveScraperTarget = () => async (
const { const {
dataLoading: { dataLoading: {
dataLoaders: { dataLoaders: {
scraperTarget: {url, id}, scraperTarget: {url, id, name},
}, },
steps: {bucketID, orgID}, steps: {bucketID, orgID},
}, },
@ -437,7 +448,7 @@ export const saveScraperTarget = () => async (
await client.scrapers.update(id, {url, bucketID}) await client.scrapers.update(id, {url, bucketID})
} else { } else {
const newTarget = await client.scrapers.create({ const newTarget = await client.scrapers.create({
name: 'new target', name,
type: ScraperTargetRequest.TypeEnum.Prometheus, type: ScraperTargetRequest.TypeEnum.Prometheus,
url, url,
bucketID, bucketID,

View File

@ -31,8 +31,6 @@ class ConfigureDataSourceSwitcher extends PureComponent<Props> {
<Scraping onClickNext={onClickNext} buckets={buckets} /> <Scraping onClickNext={onClickNext} buckets={buckets} />
</div> </div>
) )
case DataLoaderType.CSV:
return <div>{DataLoaderType.CSV}</div>
default: default:
return <EmptyDataSourceState /> return <EmptyDataSourceState />
} }

View File

@ -1,31 +0,0 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
// Components
import {ScraperTarget} from 'src/dataLoaders/components/configureStep/ScraperTarget'
const setup = (override = {}) => {
const props = {
bucket: '',
buckets: [],
onSelectBucket: jest.fn(),
onChangeURL: jest.fn(),
url: '',
...override,
}
const wrapper = shallow(<ScraperTarget {...props} />)
return {wrapper}
}
describe('ScraperTarget', () => {
describe('rendering', () => {
it('renders!', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
})
})

View File

@ -0,0 +1,54 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
// Components
import {ScraperTarget} from 'src/dataLoaders/components/configureStep/ScraperTarget'
const setup = (override = {}) => {
const props = {
bucket: '',
buckets: [],
onSelectBucket: jest.fn(),
onChangeURL: jest.fn(),
onChangeName: jest.fn(),
url: '',
name: '',
...override,
}
const wrapper = shallow(<ScraperTarget {...props} />)
return {wrapper}
}
describe('ScraperTarget', () => {
describe('rendering', () => {
it('renders correctly with no bucket, url, name', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
it('renders correctly with bucket', () => {
const {wrapper} = setup({bucket: 'defbuck'})
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
it('renders correctly with url', () => {
const {wrapper} = setup({url: 'http://url.com'})
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
it('renders correctly with name', () => {
const {wrapper} = setup({name: 'MyScraper'})
expect(wrapper.exists()).toBe(true)
expect(wrapper).toMatchSnapshot()
})
})
})

View File

@ -19,7 +19,9 @@ interface Props {
buckets: Bucket[] buckets: Bucket[]
onSelectBucket: (bucket: Bucket) => void onSelectBucket: (bucket: Bucket) => void
onChangeURL: (value: string) => void onChangeURL: (value: string) => void
onChangeName: (value: string) => void
url: string url: string
name: string
} }
export class ScraperTarget extends PureComponent<Props> { export class ScraperTarget extends PureComponent<Props> {
@ -28,11 +30,25 @@ export class ScraperTarget extends PureComponent<Props> {
} }
public render() { public render() {
const {onSelectBucket, url, bucket, buckets} = this.props const {onSelectBucket, url, name, bucket, buckets} = this.props
return ( return (
<Grid> <Grid>
<Grid.Row> <Grid.Row>
<Grid.Column widthXS={Columns.Eight} offsetXS={Columns.Two}> <Grid.Column widthXS={Columns.Eight} offsetXS={Columns.Two}>
<Form.Element
label="Name"
errorMessage={this.nameEmpty && 'Target name is empty'}
>
<Input
type={InputType.Text}
value={name}
onChange={this.handleChangeName}
titleText="Name"
size={ComponentSize.Medium}
autoFocus={true}
status={this.nameStatus}
/>
</Form.Element>
<Form.Element label="Bucket"> <Form.Element label="Bucket">
<BucketDropdown <BucketDropdown
selected={bucket} selected={bucket}
@ -44,7 +60,7 @@ export class ScraperTarget extends PureComponent<Props> {
<Grid.Column widthXS={Columns.Eight} offsetXS={Columns.Two}> <Grid.Column widthXS={Columns.Eight} offsetXS={Columns.Two}>
<Form.Element <Form.Element
label="Target URL" label="Target URL"
errorMessage={this.urlEmpty && 'target URL is empty'} errorMessage={this.urlEmpty && 'Target URL is empty'}
> >
<Input <Input
type={InputType.Text} type={InputType.Text}
@ -52,7 +68,6 @@ export class ScraperTarget extends PureComponent<Props> {
onChange={this.handleChangeURL} onChange={this.handleChangeURL}
titleText="Target URL" titleText="Target URL"
size={ComponentSize.Medium} size={ComponentSize.Medium}
autoFocus={true}
status={this.urlStatus} status={this.urlStatus}
/> />
</Form.Element> </Form.Element>
@ -73,10 +88,25 @@ export class ScraperTarget extends PureComponent<Props> {
return !this.props.url return !this.props.url
} }
private get nameStatus(): ComponentStatus {
if (this.nameEmpty) {
return ComponentStatus.Error
}
return ComponentStatus.Default
}
private get nameEmpty(): boolean {
return !this.props.name
}
private handleChangeURL = (e: ChangeEvent<HTMLInputElement>) => { private handleChangeURL = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value const value = e.target.value
this.props.onChangeURL(value) this.props.onChangeURL(value)
} }
private handleChangeName = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
this.props.onChangeName(value)
}
} }
export default ScraperTarget export default ScraperTarget

View File

@ -13,6 +13,7 @@ import ScraperTarget from 'src/dataLoaders/components/configureStep/ScraperTarge
import { import {
setScraperTargetBucket, setScraperTargetBucket,
setScraperTargetURL, setScraperTargetURL,
setScraperTargetName,
saveScraperTarget, saveScraperTarget,
} from 'src/dataLoaders/actions/dataLoaders' } from 'src/dataLoaders/actions/dataLoaders'
import {setBucketInfo} from 'src/dataLoaders/actions/steps' import {setBucketInfo} from 'src/dataLoaders/actions/steps'
@ -29,6 +30,7 @@ interface OwnProps {
interface DispatchProps { interface DispatchProps {
onSetScraperTargetBucket: typeof setScraperTargetBucket onSetScraperTargetBucket: typeof setScraperTargetBucket
onSetScraperTargetURL: typeof setScraperTargetURL onSetScraperTargetURL: typeof setScraperTargetURL
onSetScraperTargetName: typeof setScraperTargetName
onSaveScraperTarget: typeof saveScraperTarget onSaveScraperTarget: typeof saveScraperTarget
onSetBucketInfo: typeof setBucketInfo onSetBucketInfo: typeof setBucketInfo
} }
@ -37,6 +39,7 @@ interface StateProps {
scraperBucket: string scraperBucket: string
url: string url: string
currentBucket: string currentBucket: string
name: string
} }
type Props = OwnProps & DispatchProps & StateProps type Props = OwnProps & DispatchProps & StateProps
@ -56,7 +59,14 @@ export class Scraping extends PureComponent<Props> {
} }
public render() { public render() {
const {scraperBucket, onSetScraperTargetURL, url, buckets} = this.props const {
scraperBucket,
onSetScraperTargetURL,
onSetScraperTargetName,
url,
buckets,
name,
} = this.props
return ( return (
<Form onSubmit={this.handleSubmit}> <Form onSubmit={this.handleSubmit}>
@ -73,7 +83,9 @@ export class Scraping extends PureComponent<Props> {
buckets={buckets} buckets={buckets}
onSelectBucket={this.handleSelectBucket} onSelectBucket={this.handleSelectBucket}
onChangeURL={onSetScraperTargetURL} onChangeURL={onSetScraperTargetURL}
onChangeName={onSetScraperTargetName}
url={url} url={url}
name={name}
/> />
</div> </div>
</FancyScrollbar> </FancyScrollbar>
@ -97,7 +109,8 @@ export class Scraping extends PureComponent<Props> {
private get nextButtonStatus(): ComponentStatus { private get nextButtonStatus(): ComponentStatus {
if ( if (
this.props.url === '' || !this.props.url ||
!this.props.name ||
!this.props.buckets || !this.props.buckets ||
!this.props.buckets.length !this.props.buckets.length
) { ) {
@ -123,6 +136,7 @@ const mstp = ({
currentBucket: bucket, currentBucket: bucket,
scraperBucket: scraperTarget.bucket, scraperBucket: scraperTarget.bucket,
url: scraperTarget.url, url: scraperTarget.url,
name: scraperTarget.name,
} }
} }
@ -131,6 +145,7 @@ const mdtp: DispatchProps = {
onSetScraperTargetURL: setScraperTargetURL, onSetScraperTargetURL: setScraperTargetURL,
onSaveScraperTarget: saveScraperTarget, onSaveScraperTarget: saveScraperTarget,
onSetBucketInfo: setBucketInfo, onSetBucketInfo: setBucketInfo,
onSetScraperTargetName: setScraperTargetName,
} }
export default connect<StateProps, DispatchProps, OwnProps>( export default connect<StateProps, DispatchProps, OwnProps>(

View File

@ -1,46 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScraperTarget rendering renders! 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
label="Bucket"
>
<BucketsDropdown
buckets={Array []}
onSelectBucket={[MockFunction]}
selected=""
/>
</FormElement>
</GridColumn>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="target URL is empty"
label="Target URL"
>
<Input
autoFocus={true}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Target URL"
type="text"
value=""
/>
</FormElement>
</GridColumn>
</GridRow>
</Grid>
`;

View File

@ -0,0 +1,257 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScraperTarget rendering renders correctly with bucket 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target name is empty"
label="Name"
>
<Input
autoFocus={true}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Name"
type="text"
value=""
/>
</FormElement>
<FormElement
label="Bucket"
>
<BucketsDropdown
buckets={Array []}
onSelectBucket={[MockFunction]}
selected="defbuck"
/>
</FormElement>
</GridColumn>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target URL is empty"
label="Target URL"
>
<Input
autoFocus={false}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Target URL"
type="text"
value=""
/>
</FormElement>
</GridColumn>
</GridRow>
</Grid>
`;
exports[`ScraperTarget rendering renders correctly with name 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage={false}
label="Name"
>
<Input
autoFocus={true}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="default"
titleText="Name"
type="text"
value="MyScraper"
/>
</FormElement>
<FormElement
label="Bucket"
>
<BucketsDropdown
buckets={Array []}
onSelectBucket={[MockFunction]}
selected=""
/>
</FormElement>
</GridColumn>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target URL is empty"
label="Target URL"
>
<Input
autoFocus={false}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Target URL"
type="text"
value=""
/>
</FormElement>
</GridColumn>
</GridRow>
</Grid>
`;
exports[`ScraperTarget rendering renders correctly with no bucket, url, name 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target name is empty"
label="Name"
>
<Input
autoFocus={true}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Name"
type="text"
value=""
/>
</FormElement>
<FormElement
label="Bucket"
>
<BucketsDropdown
buckets={Array []}
onSelectBucket={[MockFunction]}
selected=""
/>
</FormElement>
</GridColumn>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target URL is empty"
label="Target URL"
>
<Input
autoFocus={false}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Target URL"
type="text"
value=""
/>
</FormElement>
</GridColumn>
</GridRow>
</Grid>
`;
exports[`ScraperTarget rendering renders correctly with url 1`] = `
<Grid>
<GridRow>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage="Target name is empty"
label="Name"
>
<Input
autoFocus={true}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="error"
titleText="Name"
type="text"
value=""
/>
</FormElement>
<FormElement
label="Bucket"
>
<BucketsDropdown
buckets={Array []}
onSelectBucket={[MockFunction]}
selected=""
/>
</FormElement>
</GridColumn>
<GridColumn
offsetXS={2}
widthXS={8}
>
<FormElement
errorMessage={false}
label="Target URL"
>
<Input
autoFocus={false}
autocomplete="off"
disabledTitleText="This input is disabled"
name=""
onChange={[Function]}
placeholder=""
size="md"
spellCheck={false}
status="default"
titleText="Target URL"
type="text"
value="http://url.com"
/>
</FormElement>
</GridColumn>
</GridRow>
</Grid>
`;

View File

@ -23,6 +23,7 @@ import {
setScraperTargetURL, setScraperTargetURL,
setScraperTargetID, setScraperTargetID,
setTelegrafConfigName, setTelegrafConfigName,
setScraperTargetName,
} from 'src/dataLoaders/actions/dataLoaders' } from 'src/dataLoaders/actions/dataLoaders'
// Mock Data // Mock Data
@ -428,7 +429,11 @@ describe('dataLoader reducer', () => {
const expected = { const expected = {
...INITIAL_STATE, ...INITIAL_STATE,
scraperTarget: {bucket: 'a', url: QUICKSTART_SCRAPER_TARGET_URL}, scraperTarget: {
bucket: 'a',
url: QUICKSTART_SCRAPER_TARGET_URL,
name: 'Name this Scraper Target',
},
} }
expect(actual).toEqual(expected) expect(actual).toEqual(expected)
@ -438,32 +443,50 @@ describe('dataLoader reducer', () => {
const actual = dataLoadersReducer( const actual = dataLoadersReducer(
{ {
...INITIAL_STATE, ...INITIAL_STATE,
scraperTarget: {bucket: '', url: ''}, scraperTarget: {bucket: '', url: '', name: ''},
}, },
setScraperTargetURL('http://googledoodle.com') setScraperTargetURL('http://googledoodle.com')
) )
const expected = { const expected = {
...INITIAL_STATE, ...INITIAL_STATE,
scraperTarget: {bucket: '', url: 'http://googledoodle.com'}, scraperTarget: {bucket: '', url: 'http://googledoodle.com', name: ''},
} }
expect(actual).toEqual(expected) expect(actual).toEqual(expected)
}) })
it('can set scraperTarget url', () => { it('can set scraperTarget id', () => {
const id = '12345678' const id = '12345678'
const actual = dataLoadersReducer( const actual = dataLoadersReducer(
{ {
...INITIAL_STATE, ...INITIAL_STATE,
scraperTarget: {bucket: '', url: ''}, scraperTarget: {bucket: '', url: '', name: ''},
}, },
setScraperTargetID(id) setScraperTargetID(id)
) )
const expected = { const expected = {
...INITIAL_STATE, ...INITIAL_STATE,
scraperTarget: {bucket: '', url: '', id}, scraperTarget: {bucket: '', url: '', name: '', id},
}
expect(actual).toEqual(expected)
})
it('can set scraperTarget name', () => {
const name = 'MyTarget'
const actual = dataLoadersReducer(
{
...INITIAL_STATE,
scraperTarget: {bucket: '', url: '', name: ''},
},
setScraperTargetName(name)
)
const expected = {
...INITIAL_STATE,
scraperTarget: {bucket: '', url: '', name},
} }
expect(actual).toEqual(expected) expect(actual).toEqual(expected)

View File

@ -35,7 +35,11 @@ export const INITIAL_STATE: DataLoadersState = {
precision: WritePrecision.Ns, precision: WritePrecision.Ns,
telegrafConfigID: null, telegrafConfigID: null,
pluginBundles: [], pluginBundles: [],
scraperTarget: {bucket: '', url: QUICKSTART_SCRAPER_TARGET_URL}, scraperTarget: {
bucket: '',
url: QUICKSTART_SCRAPER_TARGET_URL,
name: 'Name this Scraper Target',
},
telegrafConfigName: 'Name this Configuration', telegrafConfigName: 'Name this Configuration',
} }
@ -270,6 +274,12 @@ export default (state = INITIAL_STATE, action: Action): DataLoadersState => {
...state, ...state,
telegrafConfigName: action.payload.name, telegrafConfigName: action.payload.name,
} }
case 'SET_SCRAPER_TARGET_NAME':
const {name} = action.payload
return {
...state,
scraperTarget: {...state.scraperTarget, name},
}
case 'SET_SCRAPER_TARGET_BUCKET': case 'SET_SCRAPER_TARGET_BUCKET':
const {bucket} = action.payload const {bucket} = action.payload
return { return {

View File

@ -55,6 +55,7 @@ export enum LineProtocolStep {
interface ScraperTarget { interface ScraperTarget {
bucket: string bucket: string
url: string url: string
name: string
id?: string id?: string
} }