Merge pull request #3172 from influxdata/bugfix/confirm-user-delete

Fix Flickering Confirmation Tooltip
pull/10616/head
Alex Paxton 2018-04-11 10:19:06 -07:00 committed by GitHub
commit e3e51ac4c7
9 changed files with 199 additions and 175 deletions

View File

@ -40,7 +40,7 @@
"@types/node": "^9.4.6",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.0.38",
"@types/react-router": "3",
"@types/react-router": "^3.0.15",
"@types/text-encoding": "^0.0.32",
"autoprefixer": "^6.3.1",
"babel-core": "^6.5.1",

View File

@ -2,6 +2,7 @@ import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'uuid'
import _ from 'lodash'
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
@ -15,6 +16,12 @@ class OrganizationsTable extends Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
return (
!_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState)
)
}
handleClickCreateOrganization = () => {
this.setState({isCreatingOrganization: true})
}

View File

@ -1,42 +1,54 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {withRouter} from 'react-router'
import {withRouter, InjectedRouter} from 'react-router'
import ConfirmButton from 'shared/components/ConfirmButton'
import Dropdown from 'shared/components/Dropdown'
import InputClickToEdit from 'shared/components/InputClickToEdit'
import _ from 'lodash'
import {meChangeOrganizationAsync} from 'shared/actions/auth'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import Dropdown from 'src/shared/components/Dropdown'
import InputClickToEdit from 'src/shared/components/InputClickToEdit'
import {meChangeOrganizationAsync} from 'src/shared/actions/auth'
import {DEFAULT_ORG_ID} from 'src/admin/constants/chronografAdmin'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
import {Organization} from 'src/types'
class OrganizationsTableRow extends Component {
handleChangeCurrentOrganization = async () => {
const {router, links, meChangeOrganization, organization} = this.props
interface CurrentOrganization {
name: string
id: string
}
await meChangeOrganization(links.me, {organization: organization.id})
router.push('')
interface ExternalLink {
name: string
url: string
}
interface ExternalLinks {
custom: ExternalLink[]
}
interface Links {
me: string
external: ExternalLinks
}
interface Props {
organization: Organization
currentOrganization: CurrentOrganization
onDelete: (Organization) => void
onRename: (Organization, newName: string) => void
onChooseDefaultRole: (Organization, roleName: string) => void
meChangeOrganization: (me: string, id) => void
links: Links
router: InjectedRouter
}
class OrganizationsTableRow extends PureComponent<Props, {}> {
public shouldComponentUpdate(nextProps) {
return !_.isEqual(this.props, nextProps)
}
handleUpdateOrgName = newName => {
const {organization, onRename} = this.props
onRename(organization, newName)
}
handleDeleteOrg = () => {
const {onDelete, organization} = this.props
onDelete(organization)
}
handleChooseDefaultRole = role => {
const {organization, onChooseDefaultRole} = this.props
onChooseDefaultRole(organization, role.name)
}
render() {
public render() {
const {organization, currentOrganization} = this.props
const dropdownRolesItems = USER_ROLES.map(role => ({
@ -84,38 +96,28 @@ class OrganizationsTableRow extends Component {
</div>
)
}
}
const {arrayOf, func, shape, string} = PropTypes
public handleChangeCurrentOrganization = async () => {
const {router, links, meChangeOrganization, organization} = this.props
OrganizationsTableRow.propTypes = {
organization: shape({
id: string, // when optimistically created, organization will not have an id
name: string.isRequired,
defaultRole: string.isRequired,
}).isRequired,
onDelete: func.isRequired,
onRename: func.isRequired,
onChooseDefaultRole: func.isRequired,
currentOrganization: shape({
name: string.isRequired,
id: string.isRequired,
}),
router: shape({
push: func.isRequired,
}).isRequired,
links: shape({
me: string,
external: shape({
custom: arrayOf(
shape({
name: string.isRequired,
url: string.isRequired,
})
),
}),
}),
meChangeOrganization: func.isRequired,
await meChangeOrganization(links.me, {organization: organization.id})
router.push('')
}
public handleUpdateOrgName = newName => {
const {organization, onRename} = this.props
onRename(organization, newName)
}
public handleDeleteOrg = () => {
const {onDelete, organization} = this.props
onDelete(organization)
}
public handleChooseDefaultRole = role => {
const {organization, onChooseDefaultRole} = this.props
onChooseDefaultRole(organization, role.name)
}
}
const mapDispatchToProps = dispatch => ({
@ -126,6 +128,6 @@ const mapStateToProps = ({links}) => ({
links,
})
export default connect(mapStateToProps, mapDispatchToProps)(
withRouter(OrganizationsTableRow)
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(OrganizationsTableRow)
)

View File

@ -2,6 +2,7 @@ import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'uuid'
import _ from 'lodash'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'
@ -18,6 +19,12 @@ class UsersTable extends Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
return (
!_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState)
)
}
handleChangeUserRole = (user, currentRole) => newRole => {
this.props.onUpdateUserRole(user, currentRole, newRole)
}

View File

@ -1,88 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
import ConfirmButton from 'shared/components/ConfirmButton'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
const UsersTableRow = ({
user,
organization,
onChangeUserRole,
onDelete,
meID,
}) => {
const {colRole, colProvider, colScheme} = USERS_TABLE
const dropdownRolesItems = USER_ROLES.map(r => ({
...r,
text: r.name,
}))
const currentRole = user.roles.find(
role => role.organization === organization.id
)
const userIsMe = user.id === meID
const wrappedDelete = () => {
onDelete(user)
}
const removeWarning = 'Remove this user\nfrom Current Org?'
return (
<tr className={'chronograf-admin-table--user'}>
<td>
{userIsMe ? (
<strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
) : (
<strong>{user.name}</strong>
)}
</td>
<td style={{width: colRole}}>
<span className="chronograf-user--role">
<Dropdown
items={dropdownRolesItems}
selected={currentRole.name}
onChoose={onChangeUserRole(user, currentRole)}
buttonColor="btn-primary"
buttonSize="btn-xs"
className="dropdown-stretch"
/>
</span>
</td>
<td style={{width: colProvider}}>{user.provider}</td>
<td style={{width: colScheme}}>{user.scheme}</td>
<td className="text-right">
<ConfirmButton
confirmText={removeWarning}
confirmAction={wrappedDelete}
size="btn-xs"
type="btn-danger"
text="Remove"
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
const {func, shape, string} = PropTypes
UsersTableRow.propTypes = {
user: shape(),
organization: shape({
name: string.isRequired,
id: string.isRequired,
}),
onChangeUserRole: func.isRequired,
onDelete: func.isRequired,
meID: string.isRequired,
}
export default UsersTableRow

View File

@ -0,0 +1,103 @@
import React, {PureComponent} from 'react'
import Dropdown from 'src/shared/components/Dropdown'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
import {USERS_TABLE} from 'src/admin/constants/chronografTableSizing'
import {User, Role} from 'src/types'
interface Organization {
name: string
id: string
}
interface DropdownRole {
text: string
name: string
}
interface Props {
user: User
organization: Organization
onChangeUserRole: (User, Role) => void
onDelete: (User) => void
meID: string
}
class UsersTableRow extends PureComponent<Props> {
public render() {
const {user, onChangeUserRole} = this.props
const {colRole, colProvider, colScheme} = USERS_TABLE
return (
<tr className={'chronograf-admin-table--user'}>
<td>
{this.isMe ? (
<strong className="chronograf-user--me">
<span className="icon user" />
{user.name}
</strong>
) : (
<strong>{user.name}</strong>
)}
</td>
<td style={{width: colRole}}>
<span className="chronograf-user--role">
<Dropdown
items={this.rolesDropdownItems}
selected={this.currentRole.name}
onChoose={onChangeUserRole(user, this.currentRole)}
buttonColor="btn-primary"
buttonSize="btn-xs"
className="dropdown-stretch"
/>
</span>
</td>
<td style={{width: colProvider}}>{user.provider}</td>
<td style={{width: colScheme}}>{user.scheme}</td>
<td className="text-right">
<ConfirmButton
confirmText={this.confirmationText}
confirmAction={this.handleDelete}
size="btn-xs"
type="btn-danger"
text="Remove"
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
private handleDelete = (): void => {
const {user, onDelete} = this.props
onDelete(user)
}
private get rolesDropdownItems(): DropdownRole[] {
return USER_ROLES.map(r => ({
...r,
text: r.name,
}))
}
private get currentRole(): Role {
const {user, organization} = this.props
return user.roles.find(role => role.organization === organization.id)
}
private get isMe(): boolean {
const {user, meID} = this.props
return user.id === meID
}
private get confirmationText(): string {
return 'Remove this user\nfrom Current Org?'
}
}
export default UsersTableRow

View File

@ -1,4 +1,4 @@
import React, {Component} from 'react'
import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
@ -8,7 +8,7 @@ import {notify as notifyAction} from 'shared/actions/notifications'
import UsersTable from 'src/admin/components/chronograf/UsersTable'
class UsersPage extends Component {
class UsersPage extends PureComponent {
constructor(props) {
super(props)

View File

@ -37,37 +37,18 @@ class ConfirmButton extends PureComponent<Props, State> {
}
public render() {
const {
text,
confirmText,
type,
size,
square,
icon,
disabled,
customClass,
} = this.props
const {expanded} = this.state
const customClassString = customClass ? ` ${customClass}` : ''
const squareString = square ? ' btn-square' : ''
const expandedString = expanded ? ' active' : ''
const disabledString = disabled ? ' disabled' : ''
const classname = `confirm-button btn ${type} ${size}${customClassString}${squareString}${expandedString}${disabledString}`
const {text, confirmText, icon} = this.props
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div
className={classname}
className={this.className}
onClick={this.handleButtonClick}
ref={r => (this.buttonDiv = r)}
>
{icon && <span className={`icon ${icon}`} />}
{text && text}
<div
className={`confirm-button--tooltip ${this.calculatePosition()}`}
>
<div className={`confirm-button--tooltip ${this.calculatedPosition}`}>
<div
className="confirm-button--confirmation"
onClick={this.handleConfirmClick}
@ -81,6 +62,18 @@ class ConfirmButton extends PureComponent<Props, State> {
)
}
private get className() {
const {type, size, square, disabled, customClass} = this.props
const {expanded} = this.state
const customClassString = customClass ? ` ${customClass}` : ''
const squareString = square ? ' btn-square' : ''
const expandedString = expanded ? ' active' : ''
const disabledString = disabled ? ' disabled' : ''
return `confirm-button btn ${type} ${size}${customClassString}${squareString}${expandedString}${disabledString}`
}
private handleButtonClick = () => {
if (this.props.disabled) {
return
@ -97,7 +90,7 @@ class ConfirmButton extends PureComponent<Props, State> {
this.setState({expanded: false})
}
private calculatePosition = () => {
private get calculatedPosition() {
if (!this.buttonDiv || !this.tooltipDiv) {
return ''
}

View File

@ -61,7 +61,7 @@
version "15.5.2"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1"
"@types/react-router@3":
"@types/react-router@^3.0.15":
version "3.0.15"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-3.0.15.tgz#b55b0dc5ad8f6fa66b609f0efc390b191381d082"
dependencies: