Merge pull request #3172 from influxdata/bugfix/confirm-user-delete
Fix Flickering Confirmation Tooltippull/10616/head
commit
e3e51ac4c7
|
@ -40,7 +40,7 @@
|
||||||
"@types/node": "^9.4.6",
|
"@types/node": "^9.4.6",
|
||||||
"@types/prop-types": "^15.5.2",
|
"@types/prop-types": "^15.5.2",
|
||||||
"@types/react": "^16.0.38",
|
"@types/react": "^16.0.38",
|
||||||
"@types/react-router": "3",
|
"@types/react-router": "^3.0.15",
|
||||||
"@types/text-encoding": "^0.0.32",
|
"@types/text-encoding": "^0.0.32",
|
||||||
"autoprefixer": "^6.3.1",
|
"autoprefixer": "^6.3.1",
|
||||||
"babel-core": "^6.5.1",
|
"babel-core": "^6.5.1",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, {Component} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
|
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
|
||||||
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'
|
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 = () => {
|
handleClickCreateOrganization = () => {
|
||||||
this.setState({isCreatingOrganization: true})
|
this.setState({isCreatingOrganization: true})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,54 @@
|
||||||
import React, {Component} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from 'redux'
|
import {bindActionCreators} from 'redux'
|
||||||
import {withRouter} from 'react-router'
|
import {withRouter, InjectedRouter} from 'react-router'
|
||||||
|
|
||||||
import ConfirmButton from 'shared/components/ConfirmButton'
|
import _ from 'lodash'
|
||||||
import Dropdown from 'shared/components/Dropdown'
|
|
||||||
import InputClickToEdit from 'shared/components/InputClickToEdit'
|
|
||||||
|
|
||||||
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 {DEFAULT_ORG_ID} from 'src/admin/constants/chronografAdmin'
|
||||||
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
|
import {USER_ROLES} from 'src/admin/constants/chronografAdmin'
|
||||||
|
import {Organization} from 'src/types'
|
||||||
|
|
||||||
class OrganizationsTableRow extends Component {
|
interface CurrentOrganization {
|
||||||
handleChangeCurrentOrganization = async () => {
|
name: string
|
||||||
const {router, links, meChangeOrganization, organization} = this.props
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
await meChangeOrganization(links.me, {organization: organization.id})
|
interface ExternalLink {
|
||||||
router.push('')
|
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 => {
|
public render() {
|
||||||
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() {
|
|
||||||
const {organization, currentOrganization} = this.props
|
const {organization, currentOrganization} = this.props
|
||||||
|
|
||||||
const dropdownRolesItems = USER_ROLES.map(role => ({
|
const dropdownRolesItems = USER_ROLES.map(role => ({
|
||||||
|
@ -84,38 +96,28 @@ class OrganizationsTableRow extends Component {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const {arrayOf, func, shape, string} = PropTypes
|
public handleChangeCurrentOrganization = async () => {
|
||||||
|
const {router, links, meChangeOrganization, organization} = this.props
|
||||||
|
|
||||||
OrganizationsTableRow.propTypes = {
|
await meChangeOrganization(links.me, {organization: organization.id})
|
||||||
organization: shape({
|
router.push('')
|
||||||
id: string, // when optimistically created, organization will not have an id
|
}
|
||||||
name: string.isRequired,
|
|
||||||
defaultRole: string.isRequired,
|
public handleUpdateOrgName = newName => {
|
||||||
}).isRequired,
|
const {organization, onRename} = this.props
|
||||||
onDelete: func.isRequired,
|
onRename(organization, newName)
|
||||||
onRename: func.isRequired,
|
}
|
||||||
onChooseDefaultRole: func.isRequired,
|
|
||||||
currentOrganization: shape({
|
public handleDeleteOrg = () => {
|
||||||
name: string.isRequired,
|
const {onDelete, organization} = this.props
|
||||||
id: string.isRequired,
|
onDelete(organization)
|
||||||
}),
|
}
|
||||||
router: shape({
|
|
||||||
push: func.isRequired,
|
public handleChooseDefaultRole = role => {
|
||||||
}).isRequired,
|
const {organization, onChooseDefaultRole} = this.props
|
||||||
links: shape({
|
onChooseDefaultRole(organization, role.name)
|
||||||
me: string,
|
}
|
||||||
external: shape({
|
|
||||||
custom: arrayOf(
|
|
||||||
shape({
|
|
||||||
name: string.isRequired,
|
|
||||||
url: string.isRequired,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
meChangeOrganization: func.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
@ -126,6 +128,6 @@ const mapStateToProps = ({links}) => ({
|
||||||
links,
|
links,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
export default withRouter(
|
||||||
withRouter(OrganizationsTableRow)
|
connect(mapStateToProps, mapDispatchToProps)(OrganizationsTableRow)
|
||||||
)
|
)
|
|
@ -2,6 +2,7 @@ import React, {Component} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
|
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
|
||||||
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'
|
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 => {
|
handleChangeUserRole = (user, currentRole) => newRole => {
|
||||||
this.props.onUpdateUserRole(user, currentRole, newRole)
|
this.props.onUpdateUserRole(user, currentRole, newRole)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
||||||
import React, {Component} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from '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'
|
import UsersTable from 'src/admin/components/chronograf/UsersTable'
|
||||||
|
|
||||||
class UsersPage extends Component {
|
class UsersPage extends PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
|
@ -37,37 +37,18 @@ class ConfirmButton extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {text, confirmText, icon} = this.props
|
||||||
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}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClickOutside onClickOutside={this.handleClickOutside}>
|
<ClickOutside onClickOutside={this.handleClickOutside}>
|
||||||
<div
|
<div
|
||||||
className={classname}
|
className={this.className}
|
||||||
onClick={this.handleButtonClick}
|
onClick={this.handleButtonClick}
|
||||||
ref={r => (this.buttonDiv = r)}
|
ref={r => (this.buttonDiv = r)}
|
||||||
>
|
>
|
||||||
{icon && <span className={`icon ${icon}`} />}
|
{icon && <span className={`icon ${icon}`} />}
|
||||||
{text && text}
|
{text && text}
|
||||||
<div
|
<div className={`confirm-button--tooltip ${this.calculatedPosition}`}>
|
||||||
className={`confirm-button--tooltip ${this.calculatePosition()}`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="confirm-button--confirmation"
|
className="confirm-button--confirmation"
|
||||||
onClick={this.handleConfirmClick}
|
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 = () => {
|
private handleButtonClick = () => {
|
||||||
if (this.props.disabled) {
|
if (this.props.disabled) {
|
||||||
return
|
return
|
||||||
|
@ -97,7 +90,7 @@ class ConfirmButton extends PureComponent<Props, State> {
|
||||||
this.setState({expanded: false})
|
this.setState({expanded: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculatePosition = () => {
|
private get calculatedPosition() {
|
||||||
if (!this.buttonDiv || !this.tooltipDiv) {
|
if (!this.buttonDiv || !this.tooltipDiv) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
version "15.5.2"
|
version "15.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1"
|
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"
|
version "3.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-3.0.15.tgz#b55b0dc5ad8f6fa66b609f0efc390b191381d082"
|
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-3.0.15.tgz#b55b0dc5ad8f6fa66b609f0efc390b191381d082"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue