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