mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
@@ -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';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
14
public/app/features/admin/OrgRolePicker.tsx
Normal file
14
public/app/features/admin/OrgRolePicker.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
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} />
|
||||
);
|
@@ -1,16 +1,27 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user