Forms migration: Org users page (#23372)

* Migrate UsersActionBar

* Invites table

* Migrate Users page

* Select version of OrgPicker

* OrgRolePicker to use Select only

* Fix modal issue

* Move legacy Switch

* Move from Forms folder

* Fix failing test

* Merge and fix issues

* Update OrgRole issues

* OrgUser type

* Remove unused import

* Update Snapshot
This commit is contained in:
Tobias Skarhed 2020-04-15 16:49:20 +02:00 committed by GitHub
parent 1864807b15
commit cff70b6648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 222 additions and 382 deletions

View File

@ -23,18 +23,20 @@ const getKnobs = () => {
'primary' 'primary'
), ),
disabled: boolean('Disabled', false), disabled: boolean('Disabled', false),
closeOnConfirm: boolean('Close on confirm', true),
}; };
}; };
storiesOf('General/ConfirmButton', module) storiesOf('General/ConfirmButton', module)
.addDecorator(withCenteredStory) .addDecorator(withCenteredStory)
.add('default', () => { .add('default', () => {
const { size, buttonText, confirmText, confirmVariant, disabled } = getKnobs(); const { size, buttonText, confirmText, confirmVariant, disabled, closeOnConfirm } = getKnobs();
return ( return (
<> <>
<div className="gf-form-group"> <div className="gf-form-group">
<div className="gf-form"> <div className="gf-form">
<ConfirmButton <ConfirmButton
closeOnConfirm={closeOnConfirm}
size={size} size={size}
confirmText={confirmText} confirmText={confirmText}
disabled={disabled} disabled={disabled}
@ -51,12 +53,13 @@ storiesOf('General/ConfirmButton', module)
); );
}) })
.add('with custom button', () => { .add('with custom button', () => {
const { buttonText, confirmText, confirmVariant, disabled, size } = getKnobs(); const { buttonText, confirmText, confirmVariant, disabled, size, closeOnConfirm } = getKnobs();
return ( return (
<> <>
<div className="gf-form-group"> <div className="gf-form-group">
<div className="gf-form"> <div className="gf-form">
<ConfirmButton <ConfirmButton
closeOnConfirm={closeOnConfirm}
size={size} size={size}
confirmText={confirmText} confirmText={confirmText}
disabled={disabled} disabled={disabled}

View File

@ -58,6 +58,7 @@ interface Props extends Themeable {
confirmText?: string; confirmText?: string;
disabled?: boolean; disabled?: boolean;
confirmVariant?: ButtonVariant; confirmVariant?: ButtonVariant;
closeOnConfirm?: boolean;
onConfirm(): void; onConfirm(): void;
onClick?(): void; onClick?(): void;
@ -105,6 +106,14 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
this.props.onCancel(); this.props.onCancel();
} }
}; };
onConfirm = (event: SyntheticEvent) => {
this.props.onConfirm();
if (this.props.closeOnConfirm) {
this.setState({
showConfirm: false,
});
}
};
render() { render() {
const { const {
@ -114,7 +123,6 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
disabled, disabled,
confirmText, confirmText,
confirmVariant: confirmButtonVariant, confirmVariant: confirmButtonVariant,
onConfirm,
children, children,
} = this.props; } = this.props;
const styles = getStyles(theme); const styles = getStyles(theme);
@ -147,7 +155,7 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
<Button size={size} variant="link" onClick={this.onClickCancel}> <Button size={size} variant="link" onClick={this.onClickCancel}>
Cancel Cancel
</Button> </Button>
<Button size={size} variant={confirmButtonVariant} onClick={onConfirm}> <Button size={size} variant={confirmButtonVariant} onClick={this.onConfirm}>
{confirmText} {confirmText}
</Button> </Button>
</span> </span>

View File

@ -143,6 +143,7 @@ export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup'; export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
export { Input } from './Input/Input'; export { Input } from './Input/Input';
export { FormInputSize } from './Forms/types';
export { Switch } from './Switch/Switch'; export { Switch } from './Switch/Switch';
export { Checkbox } from './Forms/Checkbox'; export { Checkbox } from './Forms/Checkbox';

View File

@ -1,14 +1,21 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { OrgRole } from '@grafana/data'; import { OrgRole } from '@grafana/data';
import { RadioButtonGroup } from '@grafana/ui'; import { Select, FormInputSize } from '@grafana/ui';
interface Props { interface Props {
value: OrgRole; value: OrgRole;
size?: FormInputSize;
onChange: (role: OrgRole) => void; onChange: (role: OrgRole) => void;
} }
const options = Object.keys(OrgRole).map(key => ({ label: key, value: key })); const options = Object.keys(OrgRole).map(key => ({ label: key, value: key }));
export const OrgRolePicker: FC<Props> = ({ value, onChange }) => ( export const OrgRolePicker: FC<Props> = ({ value, onChange, size }) => (
<RadioButtonGroup options={options} onChange={onChange} value={value} /> <Select
size={size}
value={value}
options={options}
onChange={val => onChange(val.value as OrgRole)}
placeholder="Choose role..."
/>
); );

View File

@ -1,8 +1,8 @@
import React, { createRef, PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Invitee } from 'app/types'; import { Invitee } from 'app/types';
import { revokeInvite } from './state/actions'; import { revokeInvite } from './state/actions';
import { Icon } from '@grafana/ui'; import { Button, ClipboardButton } from '@grafana/ui';
export interface Props { export interface Props {
invitee: Invitee; invitee: Invitee;
@ -10,17 +10,6 @@ export interface Props {
} }
class InviteeRow extends PureComponent<Props> { class InviteeRow extends PureComponent<Props> {
private copyUrlRef = createRef<HTMLTextAreaElement>();
copyToClipboard = () => {
const node = this.copyUrlRef.current;
if (node) {
node.select();
document.execCommand('copy');
}
};
render() { render() {
const { invitee, revokeInvite } = this.props; const { invitee, revokeInvite } = this.props;
return ( return (
@ -28,21 +17,13 @@ class InviteeRow extends PureComponent<Props> {
<td>{invitee.email}</td> <td>{invitee.email}</td>
<td>{invitee.name}</td> <td>{invitee.name}</td>
<td className="text-right"> <td className="text-right">
<button className="btn btn-inverse btn-small" onClick={this.copyToClipboard}> <ClipboardButton variant="secondary" size="sm" getText={() => invitee.url}>
<textarea
readOnly={true}
value={invitee.url}
style={{ position: 'absolute', bottom: 0, right: 0, opacity: 0, zIndex: -10 }}
ref={this.copyUrlRef}
/>
Copy Invite Copy Invite
</button> </ClipboardButton>
&nbsp; &nbsp;
</td> </td>
<td> <td>
<button className="btn btn-danger btn-small" onClick={() => revokeInvite(invitee.code)}> <Button variant="destructive" size="sm" icon="times" onClick={() => revokeInvite(invitee.code)} />
<Icon name="times" style={{ marginBottom: 0 }} />
</button>
</td> </td>
</tr> </tr>
); );

View File

@ -1,9 +1,9 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import classNames from 'classnames';
import { setUsersSearchQuery } from './state/reducers'; import { setUsersSearchQuery } from './state/reducers';
import { getInviteesCount, getUsersSearchQuery } from './state/selectors'; import { getInviteesCount, getUsersSearchQuery } from './state/selectors';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput'; import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
import { RadioButtonGroup, LinkButton } from '@grafana/ui';
export interface Props { export interface Props {
searchQuery: string; searchQuery: string;
@ -28,18 +28,10 @@ export class UsersActionBar extends PureComponent<Props> {
onShowInvites, onShowInvites,
showInvites, showInvites,
} = this.props; } = this.props;
const options = [
const pendingInvitesButtonStyle = classNames({ { label: 'Users', value: 'users' },
btn: true, { label: `Pending Invites (${pendingInvitesCount})`, value: 'invites' },
'toggle-btn': true, ];
active: showInvites,
});
const usersButtonStyle = classNames({
btn: true,
'toggle-btn': true,
active: !showInvites,
});
return ( return (
<div className="page-action-bar"> <div className="page-action-bar">
@ -53,24 +45,15 @@ export class UsersActionBar extends PureComponent<Props> {
/> />
{pendingInvitesCount > 0 && ( {pendingInvitesCount > 0 && (
<div style={{ marginLeft: '1rem' }}> <div style={{ marginLeft: '1rem' }}>
<button className={usersButtonStyle} key="users" onClick={onShowInvites}> <RadioButtonGroup value={showInvites ? 'invites' : 'users'} options={options} onChange={onShowInvites} />
Users
</button>
<button className={pendingInvitesButtonStyle} onClick={onShowInvites} key="pending-invites">
Pending Invites ({pendingInvitesCount})
</button>
</div> </div>
)} )}
<div className="page-action-bar__spacer" /> <div className="page-action-bar__spacer" />
{canInvite && ( {canInvite && <LinkButton href="org/users/invite">Invite</LinkButton>}
<a className="btn btn-primary" href="org/users/invite">
<span>Invite</span>
</a>
)}
{externalUserMngLinkUrl && ( {externalUserMngLinkUrl && (
<a className="btn btn-primary" href={externalUserMngLinkUrl} target="_blank" rel="noopener"> <LinkButton href={externalUserMngLinkUrl} target="_blank" rel="noopener">
{externalUserMngLinkName} {externalUserMngLinkName}
</a> </LinkButton>
)} )}
</div> </div>
</div> </div>

View File

@ -2,8 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Props, UsersListPage } from './UsersListPage'; import { Props, UsersListPage } from './UsersListPage';
import { Invitee, OrgUser } from 'app/types'; import { Invitee, OrgUser } from 'app/types';
import { getMockUser } from './__mocks__/userMocks'; // import { getMockUser } from './__mocks__/userMocks';
import appEvents from '../../core/app_events';
import { NavModel } from '@grafana/data'; import { NavModel } from '@grafana/data';
import { mockToolkitActionCreator } from 'test/core/redux/mocks'; import { mockToolkitActionCreator } from 'test/core/redux/mocks';
import { setUsersSearchQuery } from './state/reducers'; import { setUsersSearchQuery } from './state/reducers';
@ -60,14 +59,3 @@ describe('Render', () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });
describe('Functions', () => {
it('should emit show remove user modal', () => {
const { instance } = setup();
const mockUser = getMockUser();
instance.onRemoveUser(mockUser);
expect(appEvents.emit).toHaveBeenCalled();
});
});

View File

@ -7,8 +7,7 @@ import Page from 'app/core/components/Page/Page';
import UsersActionBar from './UsersActionBar'; import UsersActionBar from './UsersActionBar';
import UsersTable from './UsersTable'; import UsersTable from './UsersTable';
import InviteesTable from './InviteesTable'; import InviteesTable from './InviteesTable';
import { CoreEvents, Invitee, OrgUser } from 'app/types'; import { Invitee, OrgUser, OrgRole } from 'app/types';
import appEvents from 'app/core/app_events';
import { loadInvitees, loadUsers, removeUser, updateUser } from './state/actions'; import { loadInvitees, loadUsers, removeUser, updateUser } from './state/actions';
import { getNavModel } from 'app/core/selectors/navModel'; import { getNavModel } from 'app/core/selectors/navModel';
import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors'; import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors';
@ -60,24 +59,12 @@ export class UsersListPage extends PureComponent<Props, State> {
return await this.props.loadInvitees(); return await this.props.loadInvitees();
} }
onRoleChange = (role: string, user: OrgUser) => { onRoleChange = (role: OrgRole, user: OrgUser) => {
const updatedUser = { ...user, role: role }; const updatedUser = { ...user, role: role };
this.props.updateUser(updatedUser); this.props.updateUser(updatedUser);
}; };
onRemoveUser = (user: OrgUser) => {
appEvents.emit(CoreEvents.showConfirmModal, {
title: 'Delete',
text: 'Are you sure you want to delete user ' + user.login + '?',
yesText: 'Delete',
icon: 'exclamation-triangle',
onConfirm: () => {
this.props.removeUser(user.userId);
},
});
};
onShowInvites = () => { onShowInvites = () => {
this.setState(prevState => ({ this.setState(prevState => ({
showInvites: !prevState.showInvites, showInvites: !prevState.showInvites,
@ -94,7 +81,7 @@ export class UsersListPage extends PureComponent<Props, State> {
<UsersTable <UsersTable
users={users} users={users}
onRoleChange={(role, user) => this.onRoleChange(role, user)} onRoleChange={(role, user) => this.onRoleChange(role, user)}
onRemoveUser={user => this.onRemoveUser(user)} onRemoveUser={user => this.props.removeUser(user.userId)}
/> />
); );
} }

View File

@ -3,6 +3,7 @@ import { shallow } from 'enzyme';
import UsersTable, { Props } from './UsersTable'; import UsersTable, { Props } from './UsersTable';
import { OrgUser } from 'app/types'; import { OrgUser } from 'app/types';
import { getMockUsers } from './__mocks__/userMocks'; import { getMockUsers } from './__mocks__/userMocks';
import { ConfirmModal } from '@grafana/ui';
const setup = (propOverrides?: object) => { const setup = (propOverrides?: object) => {
const props: Props = { const props: Props = {
@ -31,3 +32,12 @@ describe('Render', () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });
describe('Remove modal', () => {
it('should render correct amount', () => {
const wrapper = setup({
users: getMockUsers(3),
});
expect(wrapper.find(ConfirmModal).length).toEqual(4);
});
});

View File

@ -1,16 +1,19 @@
import React, { FC } from 'react'; import React, { FC, useState } from 'react';
import { OrgUser } from 'app/types'; import { OrgUser } from 'app/types';
import { Icon } from '@grafana/ui'; import { OrgRolePicker } from '../admin/OrgRolePicker';
import { Button, ConfirmModal } from '@grafana/ui';
import { OrgRole } from '@grafana/data';
export interface Props { export interface Props {
users: OrgUser[]; users: OrgUser[];
onRoleChange: (role: string, user: OrgUser) => void; onRoleChange: (role: OrgRole, user: OrgUser) => void;
onRemoveUser: (user: OrgUser) => void; onRemoveUser: (user: OrgUser) => void;
} }
const UsersTable: FC<Props> = props => { const UsersTable: FC<Props> = props => {
const { users, onRoleChange, onRemoveUser } = props; const { users, onRoleChange, onRemoveUser } = props;
const [showRemoveModal, setShowRemoveModal] = useState<string | boolean>(false);
return ( return (
<table className="filter-table form-inline"> <table className="filter-table form-inline">
<thead> <thead>
@ -32,32 +35,29 @@ const UsersTable: FC<Props> = props => {
<img className="filter-table__avatar" src={user.avatarUrl} /> <img className="filter-table__avatar" src={user.avatarUrl} />
</td> </td>
<td>{user.login}</td> <td>{user.login}</td>
<td> <td>
<span className="ellipsis">{user.email}</span> <span className="ellipsis">{user.email}</span>
</td> </td>
<td>{user.name}</td> <td>{user.name}</td>
<td>{user.lastSeenAtAge}</td> <td>{user.lastSeenAtAge}</td>
<td>
<div className="gf-form-select-wrapper width-12"> <td className="width-8">
<select <OrgRolePicker value={user.role} onChange={newRole => onRoleChange(newRole, user)} />
value={user.role}
className="gf-form-input"
onChange={event => onRoleChange(event.target.value, user)}
>
{['Viewer', 'Editor', 'Admin'].map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</td> </td>
<td> <td>
<div onClick={() => onRemoveUser(user)} className="btn btn-danger btn-small"> <Button size="sm" variant="destructive" onClick={() => setShowRemoveModal(user.login)} icon="times" />
<Icon name="times" style={{ marginBottom: 0 }} /> <ConfirmModal
</div> body={`Are you sure you want to delete user ${user.login}?`}
confirmText="Delete"
title="Delete"
onDismiss={() => setShowRemoveModal(false)}
isOpen={user.login === showRemoveModal}
onConfirm={() => {
onRemoveUser(user);
}}
/>
</td> </td>
</tr> </tr>
); );

View File

@ -1,3 +1,5 @@
import { OrgRole, OrgUser } from 'app/types';
export const getMockUsers = (amount: number) => { export const getMockUsers = (amount: number) => {
const users = []; const users = [];
@ -15,7 +17,7 @@ export const getMockUsers = (amount: number) => {
}); });
} }
return users; return users as OrgUser[];
}; };
export const getMockUser = () => { export const getMockUser = () => {
@ -27,9 +29,9 @@ export const getMockUser = () => {
lastSeenAtAge: '', lastSeenAtAge: '',
login: `user`, login: `user`,
orgId: 1, orgId: 1,
role: 'Admin', role: 'Admin' as OrgRole,
userId: 2, userId: 2,
}; } as OrgUser;
}; };
export const getMockInvitees = (amount: number) => { export const getMockInvitees = (amount: number) => {

View File

@ -42,22 +42,22 @@ exports[`Render should render pending invites button 1`] = `
} }
} }
> >
<button <RadioButtonGroup
className="btn toggle-btn active" onChange={[MockFunction]}
key="users" options={
onClick={[MockFunction]} Array [
> Object {
Users "label": "Users",
</button> "value": "users",
<button },
className="btn toggle-btn" Object {
key="pending-invites" "label": "Pending Invites (5)",
onClick={[MockFunction]} "value": "invites",
> },
Pending Invites ( ]
5 }
) value="users"
</button> />
</div> </div>
<div <div
className="page-action-bar__spacer" className="page-action-bar__spacer"
@ -83,8 +83,7 @@ exports[`Render should show external user management button 1`] = `
<div <div
className="page-action-bar__spacer" className="page-action-bar__spacer"
/> />
<a <LinkButton
className="btn btn-primary"
href="some/url" href="some/url"
rel="noopener" rel="noopener"
target="_blank" target="_blank"
@ -110,14 +109,11 @@ exports[`Render should show invite button 1`] = `
<div <div
className="page-action-bar__spacer" className="page-action-bar__spacer"
/> />
<a <LinkButton
className="btn btn-primary"
href="org/users/invite" href="org/users/invite"
> >
<span>
Invite Invite
</span> </LinkButton>
</a>
</div> </div>
</div> </div>
`; `;

View File

@ -92,50 +92,29 @@ exports[`Render should render users table 1`] = `
user-0 test user-0 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-0?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
<tr <tr
@ -163,50 +142,29 @@ exports[`Render should render users table 1`] = `
user-1 test user-1 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-1?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
<tr <tr
@ -234,50 +192,29 @@ exports[`Render should render users table 1`] = `
user-2 test user-2 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-2?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
<tr <tr
@ -305,50 +242,29 @@ exports[`Render should render users table 1`] = `
user-3 test user-3 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-3?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
<tr <tr
@ -376,50 +292,29 @@ exports[`Render should render users table 1`] = `
user-4 test user-4 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-4?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
<tr <tr
@ -447,50 +342,29 @@ exports[`Render should render users table 1`] = `
user-5 test user-5 test
</td> </td>
<td /> <td />
<td> <td
<div className="width-8"
className="gf-form-select-wrapper width-12"
> >
<select <Component
className="gf-form-input"
onChange={[Function]} onChange={[Function]}
value="Admin" value="Admin"
> />
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td> </td>
<td> <td>
<div <Button
className="btn btn-danger btn-small" icon="times"
onClick={[Function]} onClick={[Function]}
> size="sm"
<Icon variant="destructive"
name="times" />
style={ <Component
Object { body="Are you sure you want to delete user user-5?"
"marginBottom": 0, confirmText="Delete"
} isOpen={false}
} onConfirm={[Function]}
onDismiss={[Function]}
title="Delete"
/> />
</div>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@ -9,7 +9,7 @@ export interface OrgUser {
login: string; login: string;
name: string; name: string;
orgId: number; orgId: number;
role: string; role: OrgRole;
userId: number; userId: number;
} }