Merge pull request #3001 from influxdata/chore/chronograf-admin-ts

Conversion of AllUsersPage to TypeScript add initial test
pull/2999/merge
Andrew Watkins 2018-03-16 10:52:02 -07:00 committed by GitHub
commit 458475243e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 219 additions and 80 deletions

View File

@ -1,15 +1,44 @@
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 * as adminChronografActionCreators from 'src/admin/actions/chronograf'
import * as configActionCreators from 'shared/actions/config'
import {notify as notifyAction} from 'shared/actions/notifications'
import * as configActionCreators from 'src/shared/actions/config'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable'
import {AuthLinks, User, Role, Organization} from 'src/types'
class AllUsersPage extends Component {
interface Props {
notify: () => void
links: AuthLinks
meID: string
users: User[]
organizations: Organization[]
actionsAdmin: {
loadUsersAsync: (link: string) => void
loadOrganizationsAsync: (link: string) => void
createUserAsync: (link: string, user: User) => void
updateUserAsync: (user: User, updatedUser: User, message: string) => void
deleteUserAsync: (
user: User,
deleteObj: {isAbsoluteDelete: boolean}
) => void
}
actionsConfig: {
getAuthConfigAsync: (link: string) => void
updateAuthConfigAsync: () => void
}
authConfig: {
superAdminNewUsers: boolean
}
}
interface State {
isLoading: boolean
}
export class AllUsersPage extends PureComponent<Props, State> {
constructor(props) {
super(props)
@ -23,32 +52,6 @@ class AllUsersPage extends Component {
getAuthConfigAsync(links.config.auth)
}
handleCreateUser = user => {
const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user)
}
handleUpdateUserRoles = (user, roles, successMessage) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, roles}
updateUserAsync(user, updatedUser, successMessage)
}
handleUpdateUserSuperAdmin = (user, superAdmin) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(
user,
updatedUser,
`${user.name}'s SuperAdmin status has been updated`
)
}
handleDeleteUser = user => {
const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true})
}
async componentWillMount() {
const {
links,
@ -65,65 +68,66 @@ class AllUsersPage extends Component {
this.setState({isLoading: false})
}
handleCreateUser = (user: User) => {
const {links, actionsAdmin: {createUserAsync}} = this.props
createUserAsync(links.allUsers, user)
}
handleUpdateUserRoles = (
user: User,
roles: Role[],
successMessage: string
) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, roles}
updateUserAsync(user, updatedUser, successMessage)
}
handleUpdateUserSuperAdmin = (user: User, superAdmin: boolean) => {
const {actionsAdmin: {updateUserAsync}} = this.props
const updatedUser = {...user, superAdmin}
updateUserAsync(
user,
updatedUser,
`${user.name}'s SuperAdmin status has been updated`
)
}
handleDeleteUser = (user: User) => {
const {actionsAdmin: {deleteUserAsync}} = this.props
deleteUserAsync(user, {isAbsoluteDelete: true})
}
render() {
const {
organizations,
meID,
users,
authConfig,
actionsConfig,
links,
notify,
authConfig,
actionsConfig,
organizations,
} = this.props
return (
<AllUsersTable
meID={meID}
users={users}
links={links}
notify={notify}
authConfig={authConfig}
actionsConfig={actionsConfig}
organizations={organizations}
isLoading={this.state.isLoading}
onDeleteUser={this.handleDeleteUser}
onCreateUser={this.handleCreateUser}
onUpdateUserRoles={this.handleUpdateUserRoles}
onUpdateUserSuperAdmin={this.handleUpdateUserSuperAdmin}
onDeleteUser={this.handleDeleteUser}
links={links}
authConfig={authConfig}
actionsConfig={actionsConfig}
notify={notify}
isLoading={this.state.isLoading}
/>
)
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
AllUsersPage.propTypes = {
links: shape({
users: string.isRequired,
config: shape({
auth: string.isRequired,
}).isRequired,
}),
meID: string.isRequired,
users: arrayOf(shape),
organizations: arrayOf(shape),
actionsAdmin: shape({
loadUsersAsync: func.isRequired,
loadOrganizationsAsync: func.isRequired,
createUserAsync: func.isRequired,
updateUserAsync: func.isRequired,
deleteUserAsync: func.isRequired,
}),
actionsConfig: shape({
getAuthConfigAsync: func.isRequired,
updateAuthConfigAsync: func.isRequired,
}),
authConfig: shape({
superAdminNewUsers: bool,
}),
notify: func.isRequired,
}
const mapStateToProps = ({
links,
adminChronograf: {organizations, users},

54
ui/src/types/auth.tsx Normal file
View File

@ -0,0 +1,54 @@
export interface Organization {
defaultRole: string
id: string
links: {
self: string
}
name: string
}
export interface Role {
name: string
organization: string
}
export interface User {
id: string
links: {self: string}
name: string
provider: string
roles: Role[]
scheme: string
superAdmin: boolean
}
export interface Auth {
callback: string
label: string
login: string
logout: string
name: string
}
export interface AuthConfig {
auth: string
self: string
}
export interface AuthLinks {
allUsers: string
auth: Auth[]
config: AuthConfig
dashboards: string
environment: string
external: {
statusFeed?: string
}
layouts: string
logout: string
mappings: string
me: string
organizations: string
sources: string
users: string
}

View File

@ -1,4 +1,5 @@
import {Query} from './query'
import {Source, Kapacitor} from './sources'
import {AuthLinks, Role, User, Organization} from './auth'
export {Query, Source, Kapacitor}
export {Query, Source, Kapacitor, AuthLinks, User, Organization, Role}

View File

@ -0,0 +1,49 @@
import React from 'react'
import {AllUsersPage} from 'src/admin/containers/chronograf/AllUsersPage'
import {shallow} from 'enzyme'
import {authLinks as links} from 'test/resources'
const noop = () => {}
const setup = (override = {}) => {
const props = {
links,
meID: '1',
users: [],
organizations: [],
actionsAdmin: {
loadUsersAsync: noop,
loadOrganizationsAsync: noop,
createUserAsync: noop,
updateUserAsync: noop,
deleteUserAsync: noop,
},
actionsConfig: {
getAuthConfigAsync: noop,
updateAuthConfigAsync: noop,
},
authConfig: {
superAdminNewUsers: false,
},
notify: noop,
...override,
}
const wrapper = shallow(<AllUsersPage {...props} />)
return {
wrapper,
props,
}
}
describe('Admin.Containers.Chronograf.AllUsersPage', () => {
describe('rendering', () => {
it('renders', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
})
})
})

View File

@ -1,3 +1,14 @@
export const links = {
self: '/chronograf/v1/sources/16',
kapacitors: '/chronograf/v1/sources/16/kapacitors',
proxy: '/chronograf/v1/sources/16/proxy',
queries: '/chronograf/v1/sources/16/queries',
write: '/chronograf/v1/sources/16/write',
permissions: '/chronograf/v1/sources/16/permissions',
users: '/chronograf/v1/sources/16/users',
databases: '/chronograf/v1/sources/16/dbs',
}
export const source = {
id: '16',
name: 'ssl',
@ -9,16 +20,7 @@ export const source = {
telegraf: 'telegraf',
organization: '0',
role: 'viewer',
links: {
self: '/chronograf/v1/sources/16',
kapacitors: '/chronograf/v1/sources/16/kapacitors',
proxy: '/chronograf/v1/sources/16/proxy',
queries: '/chronograf/v1/sources/16/queries',
write: '/chronograf/v1/sources/16/write',
permissions: '/chronograf/v1/sources/16/permissions',
users: '/chronograf/v1/sources/16/users',
databases: '/chronograf/v1/sources/16/dbs',
},
links,
}
export const query = {
@ -60,3 +62,32 @@ export const kapacitor = {
proxy: '/proxy/kapacitor/1',
},
}
export const authLinks = {
allUsers: '/chronograf/v1/users',
auth: [
{
callback: '/oauth/github/callback',
label: 'Github',
login: '/oauth/github/login',
logout: '/oauth/github/logout',
name: 'github',
},
],
config: {
auth: '/chronograf/v1/config/auth',
self: '/chronograf/v1/config',
},
dashboards: '/chronograf/v1/dashboards',
environment: '/chronograf/v1/env',
external: {
statusFeed: 'https://www.influxdata.com/feed/json',
},
layouts: '/chronograf/v1/layouts',
logout: '/oauth/logout',
mappings: '/chronograf/v1/mappings',
me: '/chronograf/v1/me',
organizations: '/chronograf/v1/organizations',
sources: '/chronograf/v1/sources',
users: '/chronograf/v1/organizations/default/users',
}