Commit 69259d62 by Tobias Skarhed Committed by GitHub

Forms migration: User edit (#23110)

* Forms for UserProfile

* Migrate to new Form styles

* Add remove icon

* Remove unused import

* Update public/app/features/admin/UserOrgs.tsx

* Remove comment

* Remove icon and add text

* Make every ButtonGroup unique - regardless of values

* Remove visual glitch etc.

* Fic failing typecheck
parent 6803db87
......@@ -144,7 +144,7 @@ class UnThemedConfirmButton extends PureComponent<Props, State> {
)}
<span className={styles.confirmButtonContainer}>
<span className={confirmButtonClass}>
<Button size={size} variant="secondary" onClick={this.onClickCancel}>
<Button size={size} variant="link" onClick={this.onClickCancel}>
Cancel
</Button>
<Button size={size} variant={confirmButtonVariant} onClick={onConfirm}>
......
......@@ -256,3 +256,5 @@ export const Input = React.forwardRef<HTMLInputElement, Props>((props, ref) => {
</div>
);
});
Input.displayName = 'Input';
......@@ -61,7 +61,8 @@ export function RadioButtonGroup<T>({
},
[onChange]
);
const groupName = useRef(uniqueId('radiogroup-'));
const id = uniqueId('radiogroup-');
const groupName = useRef(id);
const styles = getRadioButtonGroupStyles();
return (
......@@ -75,7 +76,7 @@ export function RadioButtonGroup<T>({
active={value === o.value}
key={o.label}
onChange={handleOnChange(o)}
id={`option-${o.value}`}
id={`option-${o.value}-${id}`}
name={groupName.current}
fullWidth
>
......
......@@ -141,3 +141,4 @@ export { ValuePicker } from './ValuePicker/ValuePicker';
export { fieldMatchersUI } from './MatchersUI/fieldMatchersUI';
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
import React, { PureComponent } from 'react';
import { AsyncSelect } from '@grafana/ui';
import { Forms } from '@grafana/ui';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { Organization } from 'app/types';
import { SelectableValue } from '@grafana/data';
......@@ -54,18 +54,16 @@ export class OrgPicker extends PureComponent<Props, State> {
const { isLoading } = this.state;
return (
<div className="org-picker">
<AsyncSelect
className={className}
isLoading={isLoading}
defaultOptions={true}
isSearchable={false}
loadOptions={this.getOrgOptions}
onChange={onSelected}
placeholder="Select organization"
noOptionsMessage={() => 'No organizations found'}
/>
</div>
<Forms.AsyncSelect
className={className}
isLoading={isLoading}
defaultOptions={true}
isSearchable={false}
loadOptions={this.getOrgOptions}
onChange={onSelected}
placeholder="Select organization"
noOptionsMessage="No organizations found"
/>
);
}
}
import React, { FC } from 'react';
import { OrgRole } from '@grafana/data';
import { RadioButtonGroup } from '@grafana/ui';
interface Props {
value: OrgRole;
onChange: (role: OrgRole) => void;
}
const options = Object.keys(OrgRole).map(key => ({ label: key, value: key }));
export const OrgRolePicker: FC<Props> = ({ value, onChange }) => (
<RadioButtonGroup options={options} onChange={onChange} value={value} />
);
import React, { PureComponent } from 'react';
import { css, cx } from 'emotion';
import { Modal, Themeable, stylesFactory, withTheme, ConfirmButton, Button } from '@grafana/ui';
import {
Modal,
Themeable,
stylesFactory,
withTheme,
ConfirmButton,
Button,
Forms,
HorizontalGroup,
Container,
} from '@grafana/ui';
import { GrafanaTheme } from '@grafana/data';
import { UserOrg, Organization } from 'app/types';
import { UserOrg, Organization, OrgRole } from 'app/types';
import { OrgPicker, OrgSelectItem } from 'app/core/components/Select/OrgPicker';
import { OrgRolePicker } from './OrgRolePicker';
interface Props {
orgs: UserOrg[];
onOrgRemove: (orgId: number) => void;
onOrgRoleChange: (orgId: number, newRole: string) => void;
onOrgAdd: (orgId: number, role: string) => void;
onOrgRoleChange: (orgId: number, newRole: OrgRole) => void;
onOrgAdd: (orgId: number, role: OrgRole) => void;
}
interface State {
......@@ -53,7 +64,7 @@ export class UserOrgs extends PureComponent<Props, State> {
</div>
<div className={addToOrgContainerClass}>
<Button variant="secondary" onClick={this.showOrgAddModal(true)}>
Add user to organization
Add user to organisation
</Button>
</div>
<AddToOrgModal isOpen={showAddOrgModal} onOrgAdd={onOrgAdd} onDismiss={this.showOrgAddModal(false)} />
......@@ -63,8 +74,6 @@ export class UserOrgs extends PureComponent<Props, State> {
}
}
const ORG_ROLES = ['Viewer', 'Editor', 'Admin'];
const getOrgRowStyles = stylesFactory((theme: GrafanaTheme) => {
return {
removeButton: css`
......@@ -85,16 +94,14 @@ interface OrgRowProps extends Themeable {
}
interface OrgRowState {
currentRole: string;
currentRole: OrgRole;
isChangingRole: boolean;
isRemovingFromOrg: boolean;
}
class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
state = {
currentRole: this.props.org.role,
isChangingRole: false,
isRemovingFromOrg: false,
};
onOrgRemove = () => {
......@@ -107,12 +114,7 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
this.setState({ isChangingRole: true, currentRole: org.role });
};
onOrgRemoveClick = () => {
this.setState({ isRemovingFromOrg: true });
};
onOrgRoleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newRole = event.target.value;
onOrgRoleChange = (newRole: OrgRole) => {
this.setState({ currentRole: newRole });
};
......@@ -121,12 +123,12 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
};
onCancelClick = () => {
this.setState({ isChangingRole: false, isRemovingFromOrg: false });
this.setState({ isChangingRole: false });
};
render() {
const { org, theme } = this.props;
const { currentRole, isChangingRole, isRemovingFromOrg } = this.state;
const { currentRole, isChangingRole } = this.state;
const styles = getOrgRowStyles(theme);
const labelClass = cx('width-16', styles.label);
......@@ -135,50 +137,35 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
<td className={labelClass}>{org.name}</td>
{isChangingRole ? (
<td>
<div className="gf-form-select-wrapper width-8">
<select value={currentRole} className="gf-form-input" onChange={this.onOrgRoleChange}>
{ORG_ROLES.map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
<OrgRolePicker value={currentRole} onChange={this.onOrgRoleChange} />
</td>
) : (
<td className="width-25">{org.role}</td>
)}
{!isRemovingFromOrg && (
<td colSpan={isChangingRole ? 2 : 1}>
<div className="pull-right">
<ConfirmButton
confirmText="Save"
onClick={this.onChangeRoleClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRoleSave}
>
Change role
</ConfirmButton>
</div>
</td>
)}
{!isChangingRole && (
<td colSpan={isRemovingFromOrg ? 2 : 1}>
<div className="pull-right">
<ConfirmButton
confirmText="Confirm removal"
confirmVariant="destructive"
onClick={this.onOrgRemoveClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove}
>
Remove from organisation
</ConfirmButton>
</div>
</td>
)}
<td colSpan={1}>
<div className="pull-right">
<ConfirmButton
confirmText="Save"
onClick={this.onChangeRoleClick}
onCancel={this.onCancelClick}
onConfirm={this.onOrgRoleSave}
>
Change role
</ConfirmButton>
</div>
</td>
<td colSpan={1}>
<div className="pull-right">
<ConfirmButton
confirmText="Confirm removal"
confirmVariant="destructive"
onCancel={this.onCancelClick}
onConfirm={this.onOrgRemove}
>
Remove from organisation
</ConfirmButton>
</div>
</td>
</tr>
);
}
......@@ -203,22 +190,22 @@ interface AddToOrgModalProps {
interface AddToOrgModalState {
selectedOrg: Organization;
role: string;
role: OrgRole;
}
export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgModalState> {
state: AddToOrgModalState = {
selectedOrg: null,
role: 'Admin',
role: OrgRole.Admin,
};
onOrgSelect = (org: OrgSelectItem) => {
this.setState({ selectedOrg: { ...org } });
};
onOrgRoleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
onOrgRoleChange = (newRole: OrgRole) => {
this.setState({
role: event.target.value,
role: newRole,
});
};
......@@ -235,36 +222,25 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
const { isOpen } = this.props;
const { role } = this.state;
const styles = getAddToOrgModalStyles();
const buttonRowClass = cx('gf-form-button-row', styles.buttonRow);
return (
<Modal className={styles.modal} title="Add to an organization" isOpen={isOpen} onDismiss={this.onCancel}>
<div className="gf-form-group">
<h6 className="">Organisation</h6>
<OrgPicker className="width-25" onSelected={this.onOrgSelect} />
</div>
<div className="gf-form-group">
<h6 className="">Role</h6>
<div className="gf-form-select-wrapper width-16">
<select value={role} className="gf-form-input" onChange={this.onOrgRoleChange}>
{ORG_ROLES.map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</div>
<div className={buttonRowClass}>
<Button variant="primary" onClick={this.onAddUserToOrg}>
Add to organization
</Button>
<Button variant="secondary" onClick={this.onCancel}>
Cancel
</Button>
</div>
<Forms.Field label="Organisation">
<OrgPicker onSelected={this.onOrgSelect} />
</Forms.Field>
<Forms.Field label="Role">
<OrgRolePicker value={role} onChange={this.onOrgRoleChange} />
</Forms.Field>
<Container padding="md">
<HorizontalGroup spacing="md" justify="center">
<Button variant="primary" onClick={this.onAddUserToOrg}>
Add to organisation
</Button>
<Button variant="secondary" onClick={this.onCancel}>
Cancel
</Button>
</HorizontalGroup>
</Container>
</Modal>
);
}
......
import React, { PureComponent } from 'react';
import { ConfirmButton } from '@grafana/ui';
import { ConfirmButton, RadioButtonGroup } from '@grafana/ui';
import { cx } from 'emotion';
interface Props {
......@@ -13,6 +13,11 @@ interface State {
currentAdminOption: string;
}
const adminOptions = [
{ label: 'Yes', value: 'YES' },
{ label: 'No', value: 'NO' },
];
export class UserPermissions extends PureComponent<Props, State> {
state = {
isEditing: false,
......@@ -36,8 +41,8 @@ export class UserPermissions extends PureComponent<Props, State> {
this.props.onGrafanaAdminChange(newIsGrafanaAdmin);
};
onAdminOptionSelect = (event: React.ChangeEvent<HTMLSelectElement>) => {
this.setState({ currentAdminOption: event.target.value });
onAdminOptionSelect = (value: string) => {
this.setState({ currentAdminOption: value });
};
render() {
......@@ -57,19 +62,11 @@ export class UserPermissions extends PureComponent<Props, State> {
{isEditing ? (
<td colSpan={2}>
<div className="gf-form-select-wrapper width-8">
<select
<RadioButtonGroup
options={adminOptions}
value={currentAdminOption}
className="gf-form-input"
onChange={this.onAdminOptionSelect}
>
{['YES', 'NO'].map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
/>
</div>
</td>
) : (
......
......@@ -3,7 +3,7 @@ import { UserDTO } from 'app/types';
import { cx, css } from 'emotion';
import { config } from 'app/core/config';
import { GrafanaTheme } from '@grafana/data';
import { ConfirmButton, Input, ConfirmModal, InputStatus, Button, stylesFactory } from '@grafana/ui';
import { ConfirmButton, ConfirmModal, InputStatus, Button, stylesFactory, Forms } from '@grafana/ui';
interface Props {
user: UserDTO;
......@@ -265,13 +265,13 @@ export class UserProfileRow extends PureComponent<UserProfileRowProps, UserProfi
<td className={labelClass}>{label}</td>
<td className="width-25" colSpan={2}>
{this.state.editing ? (
<Input
className="width-20"
<Forms.Input
size="md"
type={inputType}
defaultValue={value}
onBlur={this.onInputBlur}
onChange={this.onInputChange}
inputRef={this.setInputElem}
ref={this.setInputElem}
/>
) : (
<span>{this.props.value}</span>
......
import { TimeZone } from '@grafana/data';
import { OrgRole } from '.';
export interface OrgUser {
avatarUrl: string;
......@@ -88,7 +89,7 @@ export interface UserSession {
export interface UserOrg {
name: string;
orgId: number;
role: string;
role: OrgRole;
}
export interface UserAdminState {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment