Merge pull request #2859 from influxdata/fix/enable-save

Enable Mapping Save button when Valid
pull/2976/head
Iris Scholten 2018-03-13 10:07:47 -07:00 committed by GitHub
commit 49b9d44e5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 389 additions and 142 deletions

View File

@ -15,6 +15,9 @@
## v1.4.2.2 [2018-03-07]
### Bug Fixes
1. [#2866](https://github.com/influxdata/chronograf/pull/2866): Change hover text on delete mappings confirmation button to 'Delete'
1. [#2911](https://github.com/influxdata/chronograf/pull/2911): Fix Heroku OAuth
1. [#2859](https://github.com/influxdata/chronograf/pull/2859): Enable Mappings save button when valid
1. [#2933](https://github.com/influxdata/chronograf/pull/2933): Include url in Kapacitor connection creation requests

View File

@ -94,7 +94,7 @@ class OrganizationsTableRow extends Component {
<InputClickToEdit
value={organization.name}
wrapperClass="fancytable--td orgs-table--name"
onUpdate={this.handleUpdateOrgName}
onBlur={this.handleUpdateOrgName}
/>
<div className={defaultRoleClassName}>
<Dropdown

View File

@ -80,14 +80,14 @@ class ProvidersTableRow extends Component {
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider}
onBlur={this.handleChangeProvider}
disabled={isDefaultMapping}
tabIndex={rowIndex}
/>
<InputClickToEdit
value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg}
onBlur={this.handleChangeProviderOrg}
disabled={isDefaultMapping}
tabIndex={rowIndex}
/>

View File

@ -1,10 +1,34 @@
import React, {Component, PropTypes} from 'react'
import React, {PureComponent} from 'react'
import ConfirmButtons from 'shared/components/ConfirmButtons'
import Dropdown from 'shared/components/Dropdown'
import InputClickToEdit from 'shared/components/InputClickToEdit'
import ConfirmButtons from 'src/shared/components/ConfirmButtons'
import Dropdown from 'src/shared/components/Dropdown'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
class ProvidersTableRowNew extends Component {
type Organization = {
id: string
name: string
}
type Scheme = {
text: string
}
interface Props {
organizations: Organization[]
schemes?: Scheme[]
rowIndex?: number
onCreate: (state: State) => void
onCancel: () => void
}
interface State {
scheme: string
provider: string
providerOrganization: string
organizationId: string
}
class ProvidersTableRowNew extends PureComponent<Props, State> {
constructor(props) {
super(props)
@ -14,25 +38,31 @@ class ProvidersTableRowNew extends Component {
providerOrganization: null,
organizationId: 'default',
}
this.handleChooseScheme = this.handleChooseScheme.bind(this)
this.handleChangeProvider = this.handleChangeProvider.bind(this)
this.handleChangeProviderOrg = this.handleChangeProviderOrg.bind(this)
this.handleChooseOrganization = this.handleChooseOrganization.bind(this)
this.handleSaveNewMapping = this.handleSaveNewMapping.bind(this)
}
handleChooseScheme = scheme => {
handleChooseScheme(scheme: Scheme) {
this.setState({scheme: scheme.text})
}
handleChangeProvider = provider => {
handleChangeProvider(provider: string) {
this.setState({provider})
}
handleChangeProviderOrg = providerOrganization => {
handleChangeProviderOrg(providerOrganization: string) {
this.setState({providerOrganization})
}
handleChooseOrganization = org => {
handleChooseOrganization(org: Organization) {
this.setState({organizationId: org.id})
}
handleSaveNewMapping = () => {
handleSaveNewMapping() {
const {onCreate} = this.props
onCreate(this.state)
}
@ -62,14 +92,16 @@ class ProvidersTableRowNew extends Component {
<InputClickToEdit
value={provider}
wrapperClass="fancytable--td provider--provider"
onUpdate={this.handleChangeProvider}
onChange={this.handleChangeProvider}
onBlur={this.handleChangeProvider}
tabIndex={rowIndex}
placeholder="google"
/>
<InputClickToEdit
value={providerOrganization}
wrapperClass="fancytable--td provider--providerorg"
onUpdate={this.handleChangeProviderOrg}
onChange={this.handleChangeProviderOrg}
onBlur={this.handleChangeProviderOrg}
tabIndex={rowIndex}
placeholder="*"
/>
@ -94,23 +126,4 @@ class ProvidersTableRowNew extends Component {
}
}
const {arrayOf, func, number, shape, string} = PropTypes
ProvidersTableRowNew.propTypes = {
organizations: arrayOf(
shape({
id: string.isRequired,
name: string.isRequired,
})
).isRequired,
schemes: arrayOf(
shape({
text: string.isRequired,
})
),
rowIndex: number,
onCreate: func.isRequired,
onCancel: func.isRequired,
}
export default ProvidersTableRowNew

View File

@ -15,7 +15,7 @@ const GraphOptionsCustomizableColumn = ({
<InputClickToEdit
value={newColumnName}
wrapperClass="column-controls-input"
onUpdate={onColumnRename}
onBlur={onColumnRename}
placeholder="Rename..."
appearAsNormalInput={true}
/>

View File

@ -1,107 +0,0 @@
import React, {Component, PropTypes} from 'react'
class InputClickToEdit extends Component {
constructor(props) {
super(props)
this.state = {
isEditing: null,
value: this.props.value,
}
}
handleCancel = () => {
this.setState({
isEditing: false,
value: this.props.value,
})
}
handleInputClick = () => {
this.setState({isEditing: true})
}
handleInputBlur = e => {
const {onUpdate, value} = this.props
if (value !== e.target.value) {
onUpdate(e.target.value)
}
this.setState({isEditing: false, value: e.target.value})
}
handleKeyDown = e => {
if (e.key === 'Enter') {
this.handleInputBlur(e)
}
if (e.key === 'Escape') {
this.handleCancel()
}
}
handleFocus = e => {
e.target.select()
}
render() {
const {isEditing, value} = this.state
const {
wrapperClass: wrapper,
disabled,
tabIndex,
placeholder,
appearAsNormalInput,
} = this.props
const wrapperClass = `${wrapper}${appearAsNormalInput
? ' input-cte__normal'
: ''}`
const defaultStyle = value ? 'input-cte' : 'input-cte__empty'
return disabled
? <div className={wrapperClass}>
<div className="input-cte__disabled">
{value}
</div>
</div>
: <div className={wrapperClass}>
{isEditing
? <input
type="text"
className="form-control input-sm provider--input"
defaultValue={value}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
autoFocus={true}
onFocus={this.handleFocus}
ref={r => (this.inputRef = r)}
tabIndex={tabIndex}
spellCheck={false}
/>
: <div
className={defaultStyle}
onClick={this.handleInputClick}
onFocus={this.handleInputClick}
tabIndex={tabIndex}
>
{value || placeholder}
{appearAsNormalInput || <span className="icon pencil" />}
</div>}
</div>
}
}
const {func, bool, number, string} = PropTypes
InputClickToEdit.propTypes = {
wrapperClass: string.isRequired,
value: string,
onUpdate: func.isRequired,
disabled: bool,
tabIndex: number,
placeholder: string,
appearAsNormalInput: bool,
}
export default InputClickToEdit

View File

@ -0,0 +1,145 @@
import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
interface Props {
wrapperClass: string
value?: string
onChange?: (value: string) => void
onBlur: (value: string) => void
disabled?: boolean
tabIndex?: number
placeholder?: string
appearAsNormalInput?: boolean
}
interface State {
isEditing: boolean
initialValue: string
}
class InputClickToEdit extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
isEditing: false,
initialValue: this.props.value,
}
this.handleCancel = this.handleCancel.bind(this)
this.handleInputClick = this.handleInputClick.bind(this)
this.handleInputBlur = this.handleInputBlur.bind(this)
this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleFocus = this.handleFocus.bind(this)
}
public static defaultProps: Partial<Props> = {
tabIndex: 0,
}
handleCancel() {
const {onChange} = this.props
const {initialValue} = this.state
this.setState({
isEditing: false,
})
if (onChange) {
onChange(initialValue)
}
}
handleInputClick() {
this.setState({isEditing: true})
}
handleInputBlur(e: ChangeEvent<HTMLInputElement>) {
const {onBlur, value} = this.props
if (value !== e.target.value) {
onBlur(e.target.value)
}
this.setState({
isEditing: false,
initialValue: e.target.value,
})
}
handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
const {onBlur, value} = this.props
if (e.key === 'Enter') {
if (value !== e.currentTarget.value) {
onBlur(e.currentTarget.value)
}
this.setState({
isEditing: false,
initialValue: e.currentTarget.value,
})
}
if (e.key === 'Escape') {
this.handleCancel()
}
}
handleChange(e: ChangeEvent<HTMLInputElement>) {
const {onChange} = this.props
if (onChange) {
onChange(e.target.value)
}
}
handleFocus(e: ChangeEvent<HTMLInputElement>) {
e.target.select()
}
render() {
const {isEditing} = this.state
const {
wrapperClass: wrapper,
disabled,
tabIndex,
placeholder,
value,
appearAsNormalInput,
} = this.props
const wrapperClass = `${wrapper}${appearAsNormalInput
? ' input-cte__normal'
: ''}`
const defaultStyle = value ? 'input-cte' : 'input-cte__empty'
return disabled
? <div className={wrapperClass}>
<div data-test="disabled" className="input-cte__disabled">
{value}
</div>
</div>
: <div className={wrapperClass}>
{isEditing
? <input
type="text"
className="form-control input-sm provider--input"
defaultValue={value}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
onChange={this.handleChange}
autoFocus={true}
onFocus={this.handleFocus}
tabIndex={tabIndex}
spellCheck={false}
/>
: <div
className={defaultStyle}
onClick={this.handleInputClick}
onFocus={this.handleInputClick}
tabIndex={tabIndex}
>
{value || placeholder}
{appearAsNormalInput ||
<span data-test="icon" className="icon pencil" />}
</div>}
</div>
}
}
export default InputClickToEdit

View File

@ -0,0 +1,91 @@
import React from 'react'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import {shallow} from 'enzyme'
const setup = (override = {}) => {
const props = {
wrapperClass: '',
value: '',
onChange: () => {},
onBlur: () => {},
disabled: false,
tabIndex: 0,
placeholder: '',
appearAsNormalInput: false,
...override,
}
const defaultState = {
isEditing: false,
initialValue: '',
}
const inputClickToEdit = shallow(<InputClickToEdit {...props} />)
return {
props,
defaultState,
inputClickToEdit,
}
}
describe('Components.Shared.InputClickToEdit', () => {
describe('rendering', () => {
it('does not display input by default', () => {
const {inputClickToEdit} = setup()
const initialDiv = inputClickToEdit.children().find('div')
const inputField = inputClickToEdit.children().find('input')
const disabledDiv = inputClickToEdit
.children()
.find({'data-test': 'disabled'})
expect(initialDiv.exists()).toBe(true)
expect(inputField.exists()).toBe(false)
expect(disabledDiv.exists()).toBe(false)
})
describe('if disabled passed in as props is true', () => {
it('should show the disabled div', () => {
const disabled = true
const {inputClickToEdit} = setup({disabled})
const inputField = inputClickToEdit.children().find('input')
const disabledDiv = inputClickToEdit
.children()
.find({'data-test': 'disabled'})
expect(inputField.exists()).toBe(false)
expect(disabledDiv.exists()).toBe(true)
})
it('should not have an icon', () => {
const disabled = true
const {inputClickToEdit} = setup({disabled})
const disabledDiv = inputClickToEdit
.children()
.find({'data-test': 'disabled'})
const icon = disabledDiv.children().find({
'data-test': 'icon',
})
expect(icon.exists()).toBe(false)
})
})
})
describe('user interaction', () => {
describe('when clicking component', () => {
it('should render input field', () => {
const {inputClickToEdit} = setup()
const initialDiv = inputClickToEdit.children().find('div')
initialDiv.simulate('click')
const divAfterClick = inputClickToEdit.children().find('div')
const inputField = inputClickToEdit.children().find('input')
const isEditing = inputClickToEdit.state('isEditing')
expect(isEditing).toBe(true)
expect(divAfterClick.exists()).toBe(false)
expect(inputField.exists()).toBe(true)
})
})
})
})

View File

@ -0,0 +1,102 @@
import React from 'react'
import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew'
import ConfirmButtons from 'src/shared/components/ConfirmButtons'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import {shallow} from 'enzyme'
const setup = (override = {}) => {
const props = {
organizations: [],
schemes: [],
rowIndex: 0,
onCreate: () => {},
onCancel: jest.fn(),
...override,
}
const defaultState = {
scheme: '*',
provider: null,
providerOrganization: null,
organizationId: 'default',
}
const row = shallow(<ProvidersTableRowNew {...props} />)
return {
props,
defaultState,
row,
}
}
describe('Components.Shared.ProvidersTableRowNew', () => {
describe('user interaction', () => {
describe('provider and providerOrganization in state are null', () => {
it('should have a disabled confirm button', () => {
const organizations = [{id: 'default', name: 'default'}]
const {row} = setup({organizations})
const confirmButtons = row.find(ConfirmButtons)
const confirmButtonsDisabled = confirmButtons.prop('isDisabled')
expect(confirmButtonsDisabled).toBe(true)
})
})
describe('provider has a value in state and providerOrganization is null', () => {
it('should have a disabled confirm button', () => {
const organizations = [{id: 'default', name: 'default'}]
const {row} = setup({organizations})
const inputClickToEdits = row.find(InputClickToEdit)
const providerInput = inputClickToEdits.first()
providerInput.simulate('click')
providerInput.simulate('change', '*')
const confirmButtons = row.find(ConfirmButtons)
const confirmButtonsDisabled = confirmButtons.prop('isDisabled')
expect(confirmButtonsDisabled).toBe(true)
})
})
describe('providerOrganization has a value in state and provider is null', () => {
it('should have a disabled confirm button', () => {
const organizations = [{id: 'default', name: 'default'}]
const {row} = setup({organizations})
const inputClickToEdits = row.find(InputClickToEdit)
const providerOrganizationInput = inputClickToEdits.last()
providerOrganizationInput.simulate('click')
providerOrganizationInput.simulate('change', '*')
const confirmButtons = row.find(ConfirmButtons)
const confirmButtonsDisabled = confirmButtons.prop('isDisabled')
expect(confirmButtonsDisabled).toBe(true)
})
})
describe('provider and providerOrganization have values in state', () => {
it('should not have a disabled confirm button', () => {
const organizations = [{id: 'default', name: 'default'}]
const {row} = setup({organizations})
const inputClickToEdits = row.find(InputClickToEdit)
const providerInput = inputClickToEdits.first()
providerInput.simulate('click')
providerInput.simulate('change', '*')
const providerOrganizationInput = inputClickToEdits.last()
providerOrganizationInput.simulate('click')
providerOrganizationInput.simulate('change', '*')
const confirmButtons = row.find(ConfirmButtons)
const confirmButtonsDisabled = confirmButtons.prop('isDisabled')
expect(confirmButtonsDisabled).toBe(false)
})
})
})
})