Commit e3e2cd82 by Shavonn Brown Committed by GitHub

Rewrite user profile edit to react (#17917)

* rewrite user profile edit to react (#17525)

* disableLogin change, still need to fix tooltip

* left out disable form for other auth

* PR changes - wrapper to render, userId instead of bool, optional user in state, change provider child param order

* moved directive to angular_wrappers

* catch api error

* finally

* move user arg back to end- optional

* optional type sig
parent 50058de6
......@@ -11,6 +11,7 @@ import { ColorPicker, SeriesColorPickerPopoverWithTheme, SecretFormField, DataLi
import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
import { SearchField } from './components/search/SearchField';
import { GraphContextMenu } from 'app/plugins/panel/graph/GraphContextMenu';
import ReactProfileWrapper from 'app/features/profile/ReactProfileWrapper';
export function registerAngularDirectives() {
react2AngularDirective('sidemenu', SideMenu, []);
......@@ -87,4 +88,6 @@ export function registerAngularDirectives() {
'suggestions',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
react2AngularDirective('reactProfileWrapper', ReactProfileWrapper, []);
}
import React, { PureComponent } from 'react';
import { getBackendSrv } from '@grafana/runtime';
import { User } from 'app/types';
export interface UserAPI {
changePassword: (ChangePassword: ChangePasswordFields) => void;
changePassword: (changePassword: ChangePasswordFields) => void;
updateUserProfile: (profile: ProfileUpdateFields) => void;
loadUser: () => void;
}
interface LoadingStates {
changePassword: boolean;
loadUser: boolean;
updateUserProfile: boolean;
}
export interface ChangePasswordFields {
......@@ -15,11 +20,19 @@ export interface ChangePasswordFields {
confirmNew: string;
}
export interface ProfileUpdateFields {
name: string;
email: string;
login: string;
}
export interface Props {
children: (api: UserAPI, states: LoadingStates) => JSX.Element;
userId?: number; // passed, will load user on mount
children: (api: UserAPI, states: LoadingStates, user?: User) => JSX.Element;
}
export interface State {
user?: User;
loadingStates: LoadingStates;
}
......@@ -27,24 +40,55 @@ export class UserProvider extends PureComponent<Props, State> {
state: State = {
loadingStates: {
changePassword: false,
loadUser: true,
updateUserProfile: false,
},
};
componentDidMount() {
if (this.props.userId) {
this.loadUser();
}
}
changePassword = async (payload: ChangePasswordFields) => {
this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: true } });
await getBackendSrv().put('/api/user/password', payload);
this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: false } });
};
loadUser = async () => {
this.setState({
loadingStates: { ...this.state.loadingStates, loadUser: true },
});
const user = await getBackendSrv().get('/api/user');
this.setState({ user, loadingStates: { ...this.state.loadingStates, loadUser: Object.keys(user).length === 0 } });
};
updateUserProfile = async (payload: ProfileUpdateFields) => {
this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: true } });
await getBackendSrv()
.put('/api/user', payload)
.then(() => {
this.loadUser();
})
.catch(e => console.log(e))
.finally(() => {
this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: false } });
});
};
render() {
const { children } = this.props;
const { loadingStates } = this.state;
const { loadingStates, user } = this.state;
const api = {
changePassword: this.changePassword,
loadUser: this.loadUser,
updateUserProfile: this.updateUserProfile,
};
return <>{children(api, loadingStates)}</>;
return <>{children(api, loadingStates, user)}</>;
}
}
......
......@@ -16,15 +16,11 @@ export interface State {
}
export class ChangePasswordForm extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
oldPassword: '',
newPassword: '',
confirmNew: '',
};
}
state: State = {
oldPassword: '',
newPassword: '',
confirmNew: '',
};
onOldPasswordChange = (oldPassword: string) => {
this.setState({ oldPassword });
......
import { react2AngularDirective } from 'app/core/utils/react2angular';
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
react2AngularDirective('prefsControl', SharedPreferences, ['resourceUri']);
......@@ -3,7 +3,6 @@ import { coreModule, NavModelSrv } from 'app/core/core';
import { dateTime } from '@grafana/data';
import { UserSession } from 'app/types';
import { BackendSrv } from 'app/core/services/backend_srv';
import { ILocationService } from 'angular';
export class ProfileCtrl {
user: any;
......@@ -18,26 +17,13 @@ export class ProfileCtrl {
navModel: any;
/** @ngInject */
constructor(
private backendSrv: BackendSrv,
private contextSrv: any,
private $location: ILocationService,
navModelSrv: NavModelSrv
) {
this.getUser();
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
this.getUserSessions();
this.getUserTeams();
this.getUserOrgs();
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
}
getUser() {
this.backendSrv.get('/api/user').then((user: any) => {
this.user = user;
this.user.theme = user.theme || 'dark';
});
}
getUserSessions() {
this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => {
sessions.reverse();
......@@ -103,19 +89,6 @@ export class ProfileCtrl {
window.location.href = config.appSubUrl + '/profile';
});
}
update() {
if (!this.userForm.$valid) {
return;
}
this.backendSrv.put('/api/user/', this.user).then(() => {
this.contextSrv.user.name = this.user.name || this.user.login;
if (this.oldTheme !== this.user.theme) {
window.location.href = config.appSubUrl + this.$location.path();
}
});
}
}
coreModule.controller('ProfileCtrl', ProfileCtrl);
import React from 'react';
import { UserProvider } from 'app/core/utils/UserProvider';
import { UserProfileEditForm } from './UserProfileEditForm';
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
import { config } from '@grafana/runtime';
export const ReactProfileWrapper = () => (
<UserProvider userId={config.bootData.user.id}>
{(api, states, user) => {
return (
<>
{!states.loadUser && (
<UserProfileEditForm
updateProfile={api.updateUserProfile}
isSavingUser={states.updateUserProfile}
user={user}
/>
)}
<SharedPreferences resourceUri="user" />
</>
);
}}
</UserProvider>
);
export default ReactProfileWrapper;
import React, { PureComponent, ChangeEvent, MouseEvent } from 'react';
import { Button, FormLabel, Input, Tooltip } from '@grafana/ui';
import { User } from 'app/types';
import config from 'app/core/config';
import { ProfileUpdateFields } from 'app/core/utils/UserProvider';
export interface Props {
user: User;
isSavingUser: boolean;
updateProfile: (payload: ProfileUpdateFields) => void;
}
export interface State {
name: string;
email: string;
login: string;
}
export class UserProfileEditForm extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const {
user: { name, email, login },
} = this.props;
this.state = {
name,
email,
login,
};
}
onNameChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ name: event.target.value });
};
onEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ email: event.target.value });
};
onLoginChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ login: event.target.value });
};
onSubmitProfileUpdate = (event: MouseEvent<HTMLInputElement>) => {
event.preventDefault();
this.props.updateProfile({ ...this.state });
};
render() {
const { name, email, login } = this.state;
const { isSavingUser } = this.props;
const { disableLoginForm } = config;
return (
<>
<h3 className="page-sub-heading">Edit Profile</h3>
<form name="userForm" className="gf-form-group">
<div className="gf-form max-width-30">
<FormLabel className="width-8">Name</FormLabel>
<Input className="gf-form-input max-width-22" type="text" onChange={this.onNameChange} value={name} />
</div>
<div className="gf-form max-width-30">
<FormLabel className="width-8">Email</FormLabel>
<Input
className="gf-form-input max-width-22"
type="text"
onChange={this.onEmailChange}
value={email}
disabled={disableLoginForm}
/>
{disableLoginForm && (
<Tooltip content="Login Details Locked - managed in another system.">
<i className="fa fa-lock gf-form-icon--right-absolute" />
</Tooltip>
)}
</div>
<div className="gf-form max-width-30">
<FormLabel className="width-8">Username</FormLabel>
<Input
className="gf-form-input max-width-22"
type="text"
onChange={this.onLoginChange}
value={login}
disabled={disableLoginForm}
/>
{disableLoginForm && (
<Tooltip content="Login Details Locked - managed in another system.">
<i className="fa fa-lock gf-form-icon--right-absolute" />
</Tooltip>
)}
</div>
<div className="gf-form-button-row">
<Button variant="primary" onClick={this.onSubmitProfileUpdate} disabled={isSavingUser}>
Save
</Button>
</div>
</form>
</>
);
}
}
export default UserProfileEditForm;
import './ProfileCtrl';
import './PrefControlCtrl';
<page-header model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<h3 class="page-sub-heading">User Profile</h3>
<form name="ctrl.userForm" class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name" />
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input
class="gf-form-input max-width-22"
type="email"
ng-readonly="ctrl.readonlyLoginFields"
required
ng-model="ctrl.user.email"
/>
<i
ng-if="ctrl.readonlyLoginFields"
class="fa fa-lock gf-form-icon--right-absolute"
bs-tooltip="'Login Details Locked - managed in another system.'"
></i>
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<input
class="gf-form-input max-width-22"
type="text"
ng-readonly="ctrl.readonlyLoginFields"
required
ng-model="ctrl.user.login"
/>
<i
ng-if="ctrl.readonlyLoginFields"
class="fa fa-lock gf-form-icon--right-absolute"
bs-tooltip="'Login Details Locked - managed in another system.'"
></i>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Save</button>
</div>
</form>
<prefs-control resource-uri="'user'"></prefs-control>
<react-profile-wrapper></react-profile-wrapper>
<h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
<div class="gf-form-group" ng-show="ctrl.showTeamsList">
......
......@@ -67,6 +67,8 @@ describe('Functions', () => {
label: '',
avatarUrl: '',
login: '',
name: '',
email: '',
};
instance.onAddUserToTeam();
......
......@@ -16,6 +16,8 @@ export interface User {
label: string;
avatarUrl: string;
login: string;
email: string;
name: string;
}
export interface Invitee {
......
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