feat(ui/dataLoaders): Check if fields are required for plugin configuration

pull/10616/head
Iris Scholten 2018-12-14 10:57:39 -08:00
parent 69b59fbcb4
commit eb0a38e727
10 changed files with 171 additions and 54 deletions

View File

@ -14,6 +14,7 @@ const setup = (override = {}) => {
removeTagValue: jest.fn(),
autoFocus: true,
value: [],
helpText: '',
...override,
}

View File

@ -12,13 +12,15 @@ interface Props {
removeTagValue: (item: string, fieldName: string) => void
autoFocus: boolean
value: string[]
helpText: string
}
class ConfigFieldSwitcher extends PureComponent<Props> {
public render() {
const {fieldName, autoFocus} = this.props
const {fieldName, autoFocus, helpText} = this.props
return (
<Form.Element label={fieldName} key={fieldName}>
<Form.Element label={fieldName} key={fieldName} helpText={helpText}>
<TagInput
title={fieldName}
autoFocus={autoFocus}

View File

@ -1,17 +1,17 @@
// Libraries
import React from 'react'
import {shallow} from 'enzyme'
import {shallow, mount} from 'enzyme'
// Components
import ConfigFieldSwitcher from 'src/onboarding/components/configureStep/streaming/ConfigFieldSwitcher'
import ArrayFormElement from 'src/onboarding/components/configureStep/streaming/ArrayFormElement'
import URIFormElement from 'src/shared/components/URIFormElement'
import {Input} from 'src/clockface'
import {Input, FormElement} from 'src/clockface'
// Types
import {ConfigFieldType} from 'src/types/v2/dataLoaders'
const setup = (override = {}) => {
const setup = (override = {}, shouldMount = false) => {
const props = {
fieldName: '',
fieldType: ConfigFieldType.String,
@ -20,10 +20,13 @@ const setup = (override = {}) => {
removeTagValue: jest.fn(),
index: 0,
value: '',
isRequired: true,
...override,
}
const wrapper = shallow(<ConfigFieldSwitcher {...props} />)
const wrapper = shouldMount
? mount(<ConfigFieldSwitcher {...props} />)
: shallow(<ConfigFieldSwitcher {...props} />)
return {wrapper}
}
@ -40,29 +43,89 @@ describe('Onboarding.Components.ConfigureStep.Streaming.ConfigFieldSwitcher', ()
expect(wrapper.exists()).toBe(true)
expect(input.exists()).toBe(true)
})
describe('if not required', () => {
it('optional is displayed as help text', () => {
const fieldName = 'yo'
const fieldType = ConfigFieldType.String
const value = ''
const {wrapper} = setup({
fieldName,
fieldType,
isRequired: false,
value,
})
const input = wrapper.find(Input)
const formElement = wrapper.find(FormElement)
expect(wrapper.exists()).toBe(true)
expect(input.exists()).toBe(true)
expect(formElement.prop('helpText')).toBe('optional')
})
})
})
describe('if type is array', () => {
it('renders an array input', () => {
const fieldName = ['yo']
const fieldType = ConfigFieldType.StringArray
const {wrapper} = setup({fieldName, fieldType})
const value = []
const {wrapper} = setup({fieldName, fieldType, value}, true)
const input = wrapper.find(ArrayFormElement)
const formElement = wrapper.find(FormElement)
expect(input.exists()).toBe(true)
expect(formElement.prop('helpText')).toBe('')
})
describe('if not required', () => {
const fieldName = ['yo']
const value = []
const fieldType = ConfigFieldType.StringArray
const {wrapper} = setup(
{fieldName, fieldType, value, isRequired: false},
true
)
const input = wrapper.find(ArrayFormElement)
const formElement = wrapper.find(FormElement)
expect(wrapper.exists()).toBe(true)
expect(input.exists()).toBe(true)
expect(formElement.prop('helpText')).toBe('optional')
})
})
describe('if type is uri', () => {
it('renders a uri input ', () => {
const fieldName = ['http://google.com']
const fieldType = ConfigFieldType.Uri
const {wrapper} = setup({fieldName, fieldType})
const value = ''
const {wrapper} = setup({fieldName, fieldType, value}, true)
const input = wrapper.find(URIFormElement)
expect(wrapper.exists()).toBe(true)
expect(input.exists()).toBe(true)
})
describe('if not required', () => {
it('optional is displayed as help text', () => {
const fieldName = ['http://google.com']
const fieldType = ConfigFieldType.Uri
const value = ''
const {wrapper} = setup(
{fieldName, fieldType, value, isRequired: false},
true
)
const input = wrapper.find(URIFormElement)
expect(wrapper.exists()).toBe(true)
expect(input.exists()).toBe(true)
})
})
})
})

View File

@ -18,6 +18,7 @@ interface Props {
addTagValue: (item: string, fieldName: string) => void
removeTagValue: (item: string, fieldName: string) => void
value: string | string[]
isRequired: boolean
}
class ConfigFieldSwitcher extends PureComponent<Props> {
@ -33,6 +34,7 @@ class ConfigFieldSwitcher extends PureComponent<Props> {
autoFocus={this.autoFocus}
onChange={onChange}
value={value as string}
helpText={this.optionalText}
/>
)
case ConfigFieldType.UriArray:
@ -44,11 +46,16 @@ class ConfigFieldSwitcher extends PureComponent<Props> {
removeTagValue={this.props.removeTagValue}
autoFocus={this.autoFocus}
value={value as string[]}
helpText={this.optionalText}
/>
)
case ConfigFieldType.String:
return (
<Form.Element label={fieldName} key={fieldName}>
<Form.Element
label={fieldName}
key={fieldName}
helpText={this.optionalText}
>
<Input
name={fieldName}
autoFocus={this.autoFocus}
@ -62,6 +69,12 @@ class ConfigFieldSwitcher extends PureComponent<Props> {
}
}
private get optionalText(): string {
if (!this.props.isRequired) {
return 'optional'
}
}
private get autoFocus(): boolean {
const {index} = this.props
return index === 0

View File

@ -51,20 +51,23 @@ class PluginConfigForm extends PureComponent<Props> {
return <p>No configuration required.</p>
}
return Object.entries(configFields).map(([fieldName, fieldType], i) => {
return (
<ConfigFieldSwitcher
key={fieldName}
fieldName={fieldName}
fieldType={fieldType}
index={i}
onChange={this.handleUpdateConfigField}
value={this.getFieldValue(telegrafPlugin, fieldName, fieldType)}
addTagValue={this.handleAddConfigFieldValue}
removeTagValue={this.handleRemoveConfigFieldValue}
/>
)
})
return Object.entries(configFields).map(
([fieldName, {type: fieldType, isRequired}], i) => {
return (
<ConfigFieldSwitcher
key={fieldName}
fieldName={fieldName}
fieldType={fieldType}
index={i}
onChange={this.handleUpdateConfigField}
value={this.getFieldValue(telegrafPlugin, fieldName, fieldType)}
isRequired={isRequired}
addTagValue={this.handleAddConfigFieldValue}
removeTagValue={this.handleRemoveConfigFieldValue}
/>
)
}
)
}
private handleAddConfigFieldValue = (

View File

@ -89,7 +89,12 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputDocker.NameEnum.Docker]: {
fields: {endpoint: ConfigFieldType.String},
fields: {
endpoint: {
type: ConfigFieldType.String,
isRequired: true,
},
},
defaults: {
name: TelegrafPluginInputDocker.NameEnum.Docker,
type: TelegrafPluginInputDocker.TypeEnum.Input,
@ -97,7 +102,12 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputFile.NameEnum.File]: {
fields: {files: ConfigFieldType.StringArray},
fields: {
files: {
type: ConfigFieldType.StringArray,
isRequired: true,
},
},
defaults: {
name: TelegrafPluginInputFile.NameEnum.File,
type: TelegrafPluginInputFile.TypeEnum.Input,
@ -113,7 +123,12 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputKubernetes.NameEnum.Kubernetes]: {
fields: {url: ConfigFieldType.Uri},
fields: {
url: {
type: ConfigFieldType.Uri,
isRequired: true,
},
},
defaults: {
name: TelegrafPluginInputKubernetes.NameEnum.Kubernetes,
type: TelegrafPluginInputKubernetes.TypeEnum.Input,
@ -121,7 +136,7 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputLogParser.NameEnum.Logparser]: {
fields: {files: ConfigFieldType.StringArray},
fields: {files: {type: ConfigFieldType.StringArray, isRequired: true}},
defaults: {
name: TelegrafPluginInputLogParser.NameEnum.Logparser,
type: TelegrafPluginInputLogParser.TypeEnum.Input,
@ -169,7 +184,7 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputProcstat.NameEnum.Procstat]: {
fields: {exe: ConfigFieldType.String},
fields: {exe: {type: ConfigFieldType.String, isRequired: false}},
defaults: {
name: TelegrafPluginInputProcstat.NameEnum.Procstat,
type: TelegrafPluginInputProcstat.TypeEnum.Input,
@ -177,7 +192,7 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputPrometheus.NameEnum.Prometheus]: {
fields: {urls: ConfigFieldType.UriArray},
fields: {urls: {type: ConfigFieldType.UriArray, isRequired: true}},
defaults: {
name: TelegrafPluginInputPrometheus.NameEnum.Prometheus,
type: TelegrafPluginInputPrometheus.TypeEnum.Input,
@ -186,8 +201,8 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
[TelegrafPluginInputRedis.NameEnum.Redis]: {
fields: {
servers: ConfigFieldType.StringArray,
password: ConfigFieldType.String,
servers: {type: ConfigFieldType.StringArray, isRequired: true},
password: {type: ConfigFieldType.String, isRequired: false},
},
defaults: {
name: TelegrafPluginInputRedis.NameEnum.Redis,
@ -196,7 +211,7 @@ export const telegrafPluginsInfo: TelegrafPluginInfo = {
},
},
[TelegrafPluginInputSyslog.NameEnum.Syslog]: {
fields: {server: ConfigFieldType.String},
fields: {server: {type: ConfigFieldType.String, isRequired: true}},
defaults: {
name: TelegrafPluginInputSyslog.NameEnum.Syslog,
type: TelegrafPluginInputSyslog.TypeEnum.Input,

View File

@ -189,29 +189,36 @@ export default (state = INITIAL_STATE, action: Action): DataLoadersState => {
return {...tp, configured: ConfigurationState.Configured}
}
const plugin = getDeep<Plugin>(tp, 'plugin', createNewPlugin(name))
const {config} = plugin
const {config} = getDeep<Plugin>(
tp,
'plugin',
createNewPlugin(name)
)
let isValidConfig = true
Object.entries(configFields).forEach(configField => {
const [fieldName, fieldType] = configField
const fieldValue = config[fieldName]
Object.entries(configFields).forEach(
([fieldName, {type: fieldType, isRequired}]) => {
if (isRequired) {
const fieldValue = config[fieldName]
const isValidUri =
fieldType === ConfigFieldType.Uri &&
validateURI(fieldValue as string)
const isValidString =
fieldType === ConfigFieldType.String &&
(fieldValue as string) !== ''
const isValidArray =
(fieldType === ConfigFieldType.StringArray ||
fieldType === ConfigFieldType.UriArray) &&
(fieldValue as string[]).length
const isValidUri =
fieldType === ConfigFieldType.Uri &&
validateURI(fieldValue as string)
const isValidString =
fieldType === ConfigFieldType.String &&
(fieldValue as string) !== ''
const isValidArray =
(fieldType === ConfigFieldType.StringArray ||
fieldType === ConfigFieldType.UriArray) &&
(fieldValue as string[]).length
if (!isValidUri && !isValidString && !isValidArray) {
isValidConfig = false
if (!isValidUri && !isValidString && !isValidArray) {
isValidConfig = false
}
}
}
})
)
if (!isValidConfig || _.isEmpty(config)) {
return {...tp, configured: ConfigurationState.Unconfigured}

View File

@ -11,6 +11,7 @@ const setup = (override = {}) => {
name: '',
value: '',
onChange: jest.fn(),
helpText: '',
...override,
}

View File

@ -14,6 +14,7 @@ interface Props {
name: string
autoFocus?: boolean
value: string
helpText: string
onChange: (e: ChangeEvent<HTMLInputElement>) => void
}
@ -37,10 +38,15 @@ class URIFormElement extends PureComponent<Props, State> {
}
public render() {
const {name, value, autoFocus} = this.props
const {name, value, autoFocus, helpText} = this.props
return (
<FormElement label={name} key={name} errorMessage={this.errorMessage}>
<FormElement
label={name}
key={name}
errorMessage={this.errorMessage}
helpText={helpText}
>
<Input
name={name}
autoFocus={autoFocus}

View File

@ -174,9 +174,15 @@ export enum ConfigFieldType {
}
export interface ConfigFields {
[field: string]: ConfigFieldType
[field: string]: {
type: ConfigFieldType
isRequired: boolean
}
}
export interface TelegrafPluginInfo {
[name: string]: {fields: ConfigFields; defaults: Plugin}
[name: string]: {
fields: ConfigFields
defaults: Plugin
}
}